Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>, but not xml) // so we do a bit of a hack and parse the data as an element to pull the attributes out XmlDeclaration decl = comment.asXmlDeclaration(); // else, we couldn't parse it as a decl, so leave as a comment if (decl != null) insert = decl; } insertNode(insert); } void insert(Token.Character token) { final String data = token.getData(); insertNode(token.isCData() ? new CDataNode(data) : new TextNode(data)); } void insert(Token.Doctype d) { DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); doctypeNode.setPubSysKey(d.getPubSysKey()); insertNode(doctypeNode); } /** * If the stack contains an element with this tag's name, pop up the stack to remove the first occurrence. If not * found, skips. * * @param endTag tag to close */ private void popStackToClose(Token.EndTag endTag) { String elName = settings.normalizeTag(endTag.tagName); Element firstFound = null; for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (next.nodeName().equals(elName)) { firstFound = next; break; } } if (firstFound == null) return; // not found, skip for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); if (next == firstFound) break; } } List<Node> parseFragment(String inputFragment, String baseUri, Parser parser) { initialiseParse(new StringReader(inputFragment), baseUri, parser); runParser(); return doc.childNodes(); } List<Node> parseFragment(String inputFragment, Element context, String baseUri, Parser parser) { return parseFragment(inputFragment, baseUri, parser); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>String inputFragment, Element context, String baseUri, Parser parser) { // context may be null state = HtmlTreeBuilderState.Initial; initialiseParse(new StringReader(inputFragment), baseUri, parser); contextElement = context; fragmentParsing = true; Element root = null; if (context != null) { if (context.ownerDocument() != null) // quirks setup: doc.quirksMode(context.ownerDocument().quirksMode()); // initialise the tokeniser state: String contextTag = context.tagName(); if (StringUtil.in(contextTag, "title", "textarea")) tokeniser.transition(TokeniserState.Rcdata); else if (StringUtil.in(contextTag, "iframe", "noembed", "noframes", "style", "xmp")) tokeniser.transition(TokeniserState.Rawtext); else if (contextTag.equals("script")) tokeniser.transition(TokeniserState.ScriptData); else if (contextTag.equals(("noscript"))) tokeniser.transition(TokeniserState.Data); // if scripting enabled, rawtext else if (contextTag.equals("plaintext")) tokeniser.transition(TokeniserState.Data); else tokeniser.transition(TokeniserState.Data); // default root = new Element(Tag.valueOf("html", settings), baseUri); doc.appendChild(root); stack.add(root); resetInsertionMode(); // setup form element to nearest form on context (up ancestor chain). ensures form controls are associated // with form correctly Elements contextChain = context.parents(); contextChain.add(0, context); for (Element parent: contextChain) { if (parent instanceof FormElement) { formElement = (FormElement) parent; break; } } } runParser(); if (context != null) return root.childNodes(); else return doc.childNodes(); } @Override protected boolean process(Token token) { currentToken = token; return this.state.process(token, this); } boolean process(Token token, HtmlTreeBuilderState state) { currentToken = token; return state.process(token, this); } void transition(HtmlTreeBuilderState state) {

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> this.state = state; } HtmlTreeBuilderState state() { return state; } void markInsertionMode() { originalState = state; } HtmlTreeBuilderState originalState() { return originalState; } void framesetOk(boolean framesetOk) { this.framesetOk = framesetOk; } boolean framesetOk() { return framesetOk; } Document getDocument() { return doc; } String getBaseUri() { return baseUri; } void maybeSetBaseUri(Element base) { } Element insertStartTag(String startTagName) { Element el = new Element(Tag.valueOf(startTagName, settings), baseUri); insert(el); return el; } void insert(Element el) { insertNode(el); stack.add(el); } Element insertEmpty(Token.StartTag startTag) { Tag tag = Tag.valueOf(startTag.name(), settings); Element el = new Element(tag, baseUri, startTag.attributes); insertNode(el); if (startTag.isSelfClosing()) { if (tag.isKnownTag()) { if (!tag.isEmpty()) tokeniser.error("Tag cannot be self closing; not a void tag"); } else // unknown tag, remember this is self closing for output tag.setSelfClosing(); } return el; } FormElement insertForm(Token.StartTag startTag, boolean onStack) { Tag tag = Tag.valueOf(startTag.name(), settings); FormElement el = new FormElement(tag, baseUri, startTag.attributes); setFormElement(el); insertNode(el); if (onStack) stack.add(el); return el; } void insert(Token.Comment commentToken) { Comment comment = new Comment(commentToken.getData()); insertNode(comment); } void insert(Token.Character characterToken) { final Node node; final Element el = currentElement(); final String tagName = el.tagName(); final String data = characterToken.getData(); if (characterToken.isCData()) node = new CDataNode(data); else if (tagName.equals("script")

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> || tagName.equals("style")) node = new DataNode(data); else node = new TextNode(data); el.appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack. } private void insertNode(Node node) { // if the stack hasn't been set up yet, elements (doctype, comments) go into the doc if (stack.isEmpty()) doc.appendChild(node); else if (isFosterInserts()) insertInFosterParent(node); else currentElement().appendChild(node); // connect form controls to their form element if (node instanceof Element && ((Element) node).tag().isFormListed()) { if (formElement != null) formElement.addElement((Element) node); } } Element pop() { int size = stack.size(); return stack.remove(size-1); } void push(Element element) { stack.add(element); } ArrayList<Element> getStack() { return stack; } boolean onStack(Element el) { return isElementInQueue(stack, el); } private boolean isElementInQueue(ArrayList<Element> queue, Element element) { for (int pos = queue.size() -1; pos >= 0; pos--) { Element next = queue.get(pos); if (next == element) { return true; } } return false; } Element getFromStack(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (next.normalName().equals(elName)) { return next; } } return null; } boolean removeFromStack(Element el) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (next == el) { stack.remove(pos); return true; } } return false; } void popStackToClose(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> next = stack.get(pos); stack.remove(pos); if (next.normalName().equals(elName)) break; } } // elnames is sorted, comes from Constants void popStackToClose(String... elNames) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); stack.remove(pos); if (inSorted(next.normalName(), elNames)) break; } } void popStackToBefore(String elName) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (next.normalName().equals(elName)) { break; } else { stack.remove(pos); } } } void clearStackToTableContext() { clearStackToContext("table"); } void clearStackToTableBodyContext() { clearStackToContext("tbody", "tfoot", "thead", "template"); } void clearStackToTableRowContext() { clearStackToContext("tr", "template"); } private void clearStackToContext(String... nodeNames) { for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (StringUtil.in(next.normalName(), nodeNames) || next.normalName().equals("html")) break; else stack.remove(pos); } } Element aboveOnStack(Element el) { assert onStack(el); for (int pos = stack.size() -1; pos >= 0; pos--) { Element next = stack.get(pos); if (next == el) { return stack.get(pos-1); } } return null; } void insertOnStackAfter(Element after, Element in) { int i = stack.lastIndexOf(after); Validate.isTrue(i != -1); stack.add(i+1, in); } void replaceOnStack(Element out, Element in) { replaceInQueue(stack, out, in); } private void replaceInQueue(ArrayList<Element> queue

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>, Element out, Element in) { int i = queue.lastIndexOf(out); Validate.isTrue(i != -1); queue.set(i, in); } void resetInsertionMode() { boolean last = false; for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); if (pos == 0) { last = true; node = contextElement; } String name = node.normalName(); if ("select".equals(name)) { transition(HtmlTreeBuilderState.InSelect); break; // frag } else if (("td".equals(name) || "th".equals(name) && !last)) { transition(HtmlTreeBuilderState.InCell); break; } else if ("tr".equals(name)) { transition(HtmlTreeBuilderState.InRow); break; } else if ("tbody".equals(name) || "thead".equals(name) || "tfoot".equals(name)) { transition(HtmlTreeBuilderState.InTableBody); break; } else if ("caption".equals(name)) { transition(HtmlTreeBuilderState.InCaption); break; } else if ("colgroup".equals(name)) { transition(HtmlTreeBuilderState.InColumnGroup); break; // frag } else if ("table".equals(name)) { transition(HtmlTreeBuilderState.InTable); break; } else if ("head".equals(name)) { transition(HtmlTreeBuilderState.InBody); break; // frag } else if ("body".equals(name)) { transition(HtmlTreeBuilderState.InBody); break; } else if ("frameset".equals(name)) { transition(HtmlTreeBuilderState.InFrameset); break; // frag } else if ("html".equals(name)) { transition(HtmlTreeBuilderState.BeforeHead); break; // frag } else if (last) { transition(HtmlTreeBuilderState.InBody); break; // frag } } } // todo: tidy up in specific scope methods private String[] specificScopeTarget = {null}; private boolean inSpecificScope(String targetName, String[] baseTypes, String[]

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>equals(targetName)) return true; if (!inSorted(elName, TagSearchSelectScope)) // all elements except return false; } Validate.fail("Should not be reachable"); return false; } void setHeadElement(Element headElement) { this.headElement = headElement; } Element getHeadElement() { return headElement; } boolean isFosterInserts() { return fosterInserts; } void setFosterInserts(boolean fosterInserts) { this.fosterInserts = fosterInserts; } FormElement getFormElement() { return formElement; } void setFormElement(FormElement formElement) { this.formElement = formElement; } void newPendingTableCharacters() { pendingTableCharacters = new ArrayList<>(); } List<String> getPendingTableCharacters() { return pendingTableCharacters; } /** 11.2.5.2 Closing elements that have implied end tags<p/> When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a dt element, an li element, an option element, an optgroup element, a p element, an rp element, or an rt element, the UA must pop the current node off the stack of open elements. @param excludeTag If a step requires the UA to generate implied end tags but lists an element to exclude from the process, then the UA must perform the above steps as if that element was not in the above list. */ void generateImpliedEndTags(String excludeTag) { while ((excludeTag != null && !currentElement().normalName().equals(excludeTag)) && inSorted(currentElement().normalName(), TagSearchEndTags)) pop(); } void generateImpliedEndTags() { generateImpliedEndTags(null); } boolean isSpecial(Element el) { // todo: mathml's mi, mo, mn // todo: svg's foreigObject, desc, title String name = el.normalName(); return inSorted(name, TagSearchSpecial); } Element lastFormattingElement() { return formattingElements.size() > 0 ? formattingElements.get(formattingElements.size

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>()-1) : null; } Element removeLastFormattingElement() { int size = formattingElements.size(); if (size > 0) return formattingElements.remove(size-1); else return null; } // active formatting elements void pushActiveFormattingElements(Element in) { int numSeen = 0; for (int pos = formattingElements.size() -1; pos >= 0; pos--) { Element el = formattingElements.get(pos); if (el == null) // marker break; if (isSameFormattingElement(in, el)) numSeen++; if (numSeen == 3) { formattingElements.remove(pos); break; } } formattingElements.add(in); } private boolean isSameFormattingElement(Element a, Element b) { // same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children return a.normalName().equals(b.normalName()) && // a.namespace().equals(b.namespace()) && a.attributes().equals(b.attributes()); // todo: namespaces } void reconstructFormattingElements() { Element last = lastFormattingElement(); if (last == null || onStack(last)) return; Element entry = last; int size = formattingElements.size(); int pos = size - 1; boolean skip = false; while (true) { if (pos == 0) { // step 4. if none before, skip to 8 skip = true; break; } entry = formattingElements.get(--pos); // step 5. one earlier than entry if (entry == null || onStack(entry)) // step 6 - neither marker nor on stack break; // jump to 8, else continue back to 4 } while(true) { if (!skip) // step 7: on later than entry entry = formattingElements.get(++pos); Validate.notNull(entry); // should not occur, as we break at last element // 8. create new element from element, 9 insert into current node, onto stack skip = false; // can only skip increment from 4. Element newEl = insertStartTag(entry.normal

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Name()); // todo: avoid fostering here? // newEl.namespace(entry.namespace()); // todo: namespaces newEl.attributes().addAll(entry.attributes()); // 10. replace entry with new entry formattingElements.set(pos, newEl); // 11 if (pos == size-1) // if not last entry in list, jump to 7 break; } } void clearFormattingElementsToLastMarker() { while (!formattingElements.isEmpty()) { Element el = removeLastFormattingElement(); if (el == null) break; } } void removeFromActiveFormattingElements(Element el) { for (int pos = formattingElements.size() -1; pos >= 0; pos--) { Element next = formattingElements.get(pos); if (next == el) { formattingElements.remove(pos); break; } } } boolean isInActiveFormattingElements(Element el) { return isElementInQueue(formattingElements, el); } Element getActiveFormattingElement(String nodeName) { for (int pos = formattingElements.size() -1; pos >= 0; pos--) { Element next = formattingElements.get(pos); if (next == null) // scope marker break; else if (next.normalName().equals(nodeName)) return next; } return null; } void replaceActiveFormattingElement(Element out, Element in) { replaceInQueue(formattingElements, out, in); } void insertMarkerToFormattingElements() { formattingElements.add(null); } void insertInFosterParent(Node in) { Element fosterParent; Element lastTable = getFromStack("table"); boolean isLastTableParent = false; if (lastTable != null) { if (lastTable.parent() != null) { fosterParent = lastTable.parent(); isLastTableParent = true; } else fosterParent = aboveOnStack(lastTable); } else { // no table == frag fosterParent = stack.get(0); } if (isLastTableParent) { Validate.notNull(lastTable); // last table cannot be null by this point. lastTable.before(in);

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> * The attributes of an Element. * <p> * Attributes are treated as a map: there can be only one value associated with an attribute key/name. * </p> * <p> * Attribute name and value comparisons are generally <b>case sensitive</b>. By default for HTML, attribute names are * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by * name. * </p> * * @author Jonathan Hedley, jonathan@hedley.net */ public class Attributes implements Iterable<Attribute>, Cloneable { protected static final String dataPrefix = "data-"; private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting // manages the key/val arrays private static final int GrowthFactor = 2; private static final String[] Empty = {}; static final int NotFound = -1; private static final String EmptyString = ""; private int size = 0; // number of slots used (not capacity, which is keys.length String[] keys = Empty; String[] vals = Empty; // check there's room for more private void checkCapacity(int minNewSize) { Validate.isTrue(minNewSize >= size); int curSize = keys.length; if (curSize >= minNewSize) return; int newSize = curSize >= InitialCapacity ? size * GrowthFactor : InitialCapacity; if (minNewSize > newSize) newSize = minNewSize; keys = copyOf(keys, newSize); vals = copyOf(vals, newSize); } // simple implementation of Arrays.copy, for support of Android API 8. private static String[] copyOf(String[] orig, int size) { final String[] copy = new String[size]; System.arraycopy(orig, 0, copy, 0, Math.min(orig.length, size)); return copy; } int indexOfKey(String key) { Validate.notNull(key); for (int i = 0; i < size; i++) { if (key.equals(keys[i])) return i; } return NotFound; } private int indexOfKey

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>IgnoreCase(String key) { Validate.notNull(key); for (int i = 0; i < size; i++) { if (key.equalsIgnoreCase(keys[i])) return i; } return NotFound; } // we track boolean attributes as null in values - they're just keys. so returns empty for consumers static String checkNotNull(String val) { return val == null ? EmptyString : val; } /** Get an attribute value by key. @param key the (case-sensitive) attribute key @return the attribute value if set; or empty string if not set (or a boolean attribute). @see #hasKey(String) add(key, value); return this; } void putIgnoreCase(String key, String value) { int i = indexOfKeyIgnoreCase(key); if (i != NotFound) { vals[i] = value; if (!keys[i].equals(key)) // case changed, update keys[i] = key; } else add(key, value); } /** * Set a new boolean attribute, remove attribute if value is false. * @param key case <b>insensitive</b> attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, boolean value) { if (value) putIgnoreCase(key, null); else remove(key); return this; } /** Set a new attribute, or replace an existing one by key. @param attribute attribute with case sensitive key @return these attributes, for chaining */ public Attributes put(Attribute attribute) { Validate.notNull(attribute); put(attribute.getKey(), attribute.getValue()); attribute.parent = this; return this; } // removes and shifts up private void remove(int index) { Validate.isFalse(index >= size); int shifted = size - index - 1; if (shifted > 0) { System.arraycopy(keys, index + 1, keys, index, shifted); System.arraycopy(vals, index + 1, vals, index, shifted); } size--; keys[size] = null; // release hold vals[size] = null; } /**

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> Remove an attribute by key. <b>Case sensitive.</b> @param key attribute key to remove */ public void remove(String key) { int i = indexOfKey(key); if (i != NotFound) remove(i); } /** Remove an attribute by key. <b>Case insensitive.</b> @param key attribute key to remove */ public void removeIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); if (i != NotFound) remove(i); } /** Tests if these attributes contain an attribute with this key. @param key case-sensitive key to check for @return true if key exists, false otherwise */ public boolean hasKey(String key) { return indexOfKey(key) != NotFound; } } public Iterator<Attribute> iterator() { return new Iterator<Attribute>() { int i = 0; @Override public boolean hasNext() { return i < size; } @Override public Attribute next() { final Attribute attr = new Attribute(keys[i], vals[i], Attributes.this); i++; return attr; } @Override public void remove() { Attributes.this.remove(--i); // next() advanced, so rewind } }; } /** Get the attributes as a List, for iteration. @return an view of the attributes as an unmodifialbe List. */ public List<Attribute> asList() { ArrayList<Attribute> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { Attribute attr = vals[i] == null ? new BooleanAttribute(keys[i]) : // deprecated class, but maybe someone still wants it new Attribute(keys[i], vals[i], Attributes.this); list.add(attr); } return Collections.unmodifiableList(list); } /** * Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys * starting with {@code data-}. * @return map of custom data attributes. */ public Map<String, String> dataset() { return new Dataset(this); } /** Get the HTML representation of these attributes. @return HTML

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> @throws SerializationException if the HTML representation of the attributes cannot be constructed. */ public String html() { StringBuilder sb = StringUtil.borrowBuilder(); try { html(sb, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used } catch (IOException e) { // ought never happen throw new SerializationException(e); } return StringUtil.releaseBuilder(sb); } final void html(final Appendable accum, final Document.OutputSettings out) throws IOException { final int sz = size; for (int i = 0; i < sz; i++) { // inlined from Attribute.html() final String key = keys[i]; final String val = vals[i]; accum.append(' ').append(key); // collapse checked=null, checked="", checked=checked; write out others if (!Attribute.shouldCollapseAttribute(key, val, out)) { accum.append("=\""); Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false); accum.append('"'); } } } @Override public String toString() { return html(); } /** * Checks if these attributes are equal to another set of attributes, by comparing the two sets * @param o attributes to compare with * @return if both sets of attributes have the same content */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Attributes that = (Attributes) o; if (size != that.size) return false; if (!Arrays.equals(keys, that.keys)) return false; return Arrays.equals(vals, that.vals); } /** * Calculates the hashcode of these attributes, by iterating all attributes and summing their hashcodes. * @return calculated hashcode */ @Override public int hashCode() { int result = size; result = 31 * result + Arrays.hashCode(keys); result = 31 * result + Arrays.hashCode(vals); return result; } @Override public Attributes clone() { Attributes clone; try { clone =

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> (Attributes) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null; attributes.put(dataKey, value); return oldValue; } private class EntrySet extends AbstractSet<Map.Entry<String, String>> { @Override public Iterator<Map.Entry<String, String>> iterator() { return new DatasetIterator(); } @Override public int size() { int count = 0; Iterator iter = new DatasetIterator(); while (iter.hasNext()) count++; return count; } } private class DatasetIterator implements Iterator<Map.Entry<String, String>> { private Iterator<Attribute> attrIter = attributes.iterator(); private Attribute attr; public boolean hasNext() { while (attrIter.hasNext()) { attr = attrIter.next(); if (attr.isDataAttribute()) return true; } return false; } public Entry<String, String> next() { return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue()); } public void remove() { attributes.remove(attr.getKey()); } } } private static String dataKey(String key) { return dataPrefix + key; } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Point = bufLength > readAheadLimit ? readAheadLimit : bufLength; } } catch (IOException e) { throw new UncheckedIOException(e); } } /** * Gets the current cursor position in the content. * @return current position */ public int pos() { return readerPos + bufPos; } /** * Tests if all the content has been read. * @return true if nothing left to read. */ public boolean isEmpty() { bufferUp(); return bufPos >= bufLength; } private boolean isEmptyNoBufferUp() { return bufPos >= bufLength; } /** * Get the char at the current position. * @return char */ public char current() { bufferUp(); return isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; } char consume() { bufferUp(); char val = isEmptyNoBufferUp() ? EOF : charBuf[bufPos]; bufPos++; return val; } void unconsume() { if (bufPos < 1) throw new UncheckedIOException(new IOException("No buffer left to unconsume")); bufPos--; } /** * Moves the current position by one. */ public void advance() { bufPos++; } void mark() { // extra buffer up, to get as much rewind capacity as possible bufSplitPoint = 0; bufferUp(); bufMark = bufPos; } void rewindToMark() { if (bufMark == -1) throw new UncheckedIOException(new IOException("Mark invalid")); bufPos = bufMark; } /** * Returns the number of characters between the current position and the next instance of the input char * @param c scan target * @return offset between current position and next instance of target. -1 if not found. */ int nextIndexOf(char c) { // doesn't handle scanning for surrogates bufferUp(); for (int i = bufPos; i < bufLength; i++) { if (c == charBuf[i]) return i - bufPos; } return -1; } /** * Returns the number of characters between the current position and the next instance of the input sequence

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> * * @param seq scan target * @return offset between current position and next instance of target. -1 if not found. */ int nextIndexOf(CharSequence seq) { bufferUp(); // doesn't handle scanning for surrogates char startChar = seq.charAt(0); for (int offset = bufPos; offset < bufLength; offset++) { // scan to first instance of startchar: if (startChar != charBuf[offset]) while(++offset < bufLength && startChar != charBuf[offset]) { /* empty */ } int i = offset + 1; int last = i + seq.length()-1; if (offset < bufLength && last <= bufLength) { for (int j = 1; i < last && seq.charAt(j) == charBuf[i]; i++, j++) { /* empty */ } if (i == last) // found full sequence return offset - bufPos; } } return -1; } /** * Reads characters up to the specific char. * @param c the delimiter * @return the chars read */ public String consumeTo(char c) { int offset = nextIndexOf(c); if (offset != -1) { String consumed = cacheString(charBuf, stringCache, bufPos, offset); bufPos += offset; return consumed; } else { return consumeToEnd(); } } String consumeTo(String seq) { int offset = nextIndexOf(seq); if (offset != -1) { String consumed = cacheString(charBuf, stringCache, bufPos, offset); bufPos += offset; return consumed; } else { return consumeToEnd(); } } /** * Read characters until the first of any delimiters is found. * @param chars delimiters to scan for * @return characters read up to the matched delimiter. */ public String consumeToAny(final char... chars) { bufferUp(); int pos = bufPos; final int start = pos; final int remaining = bufLength; final char[] val = charBuf; final int charLen = chars.length; int i; OUTER: while (pos < remaining) { for (i = 0; i

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> < charLen; i++) { if (val[pos] == chars[i]) break OUTER; } pos++; } bufPos = pos; return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeToAnySorted(final char... chars) { bufferUp(); int pos = bufPos; final int start = pos; final int remaining = bufLength; final char[] val = charBuf; while (pos < remaining) { if (Arrays.binarySearch(chars, val[pos]) >= 0) break; pos++; } bufPos = pos; return bufPos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeData() { // &, <, null //bufferUp(); // no need to bufferUp, just called consume() int pos = bufPos; final int start = pos; final int remaining = bufLength; final char[] val = charBuf; OUTER: while (pos < remaining) { switch (val[pos]) { case '&': case '<': case TokeniserState.nullChar: break OUTER; default: pos++; } } bufPos = pos; return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeTagName() { // '\t', '\n', '\r', '\f', ' ', '/', '>', nullChar // NOTE: out of spec, added '<' to fix common author bugs bufferUp(); int pos = bufPos; final int start = pos; final int remaining = bufLength; final char[] val = charBuf; OUTER: while (pos < remaining) { switch (val[pos]) { case '\t': case '\n': case '\r': case '\f': case ' ': case '/': case '>': case '<': case TokeniserState.nullChar: break OUTER; } pos++; } bufPos = pos; return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : ""; } String consumeToEnd() { buffer

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Up(); String data = cacheString(charBuf, stringCache, bufPos, bufLength - bufPos); bufPos = bufLength; return data; } String consumeLetterSequence() { bufferUp(); int start = bufPos; while (bufPos < bufLength) { char c = charBuf[bufPos]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) bufPos++; else break; } return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeLetterThenDigitSequence() { bufferUp(); int start = bufPos; while (bufPos < bufLength) { char c = charBuf[bufPos]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c)) bufPos++; else break; } while (!isEmptyNoBufferUp()) { char c = charBuf[bufPos]; if (c >= '0' && c <= '9') bufPos++; else break; } return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeHexSequence() { bufferUp(); int start = bufPos; while (bufPos < bufLength) { char c = charBuf[bufPos]; if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) bufPos++; else break; } return cacheString(charBuf, stringCache, start, bufPos - start); } String consumeDigitSequence() { bufferUp(); int start = bufPos; while (bufPos < bufLength) { char c = charBuf[bufPos]; if (c >= '0' && c <= '9') bufPos++; else break; } return cacheString(charBuf, stringCache, start, bufPos - start); } boolean matches(char c) { return !isEmpty() && charBuf[bufPos] == c; }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> boolean matches(String seq) { bufferUp(); int scanLength = seq.length(); if (scanLength > bufLength - bufPos) return false; for (int offset = 0; offset < scanLength; offset++) if (seq.charAt(offset) != charBuf[bufPos +offset]) return false; return true; } boolean matchesIgnoreCase(String seq) { bufferUp(); int scanLength = seq.length(); if (scanLength > bufLength - bufPos) return false; for (int offset = 0; offset < scanLength; offset++) { char upScan = Character.toUpperCase(seq.charAt(offset)); char upTarget = Character.toUpperCase(charBuf[bufPos + offset]); if (upScan != upTarget) return false; } return true; } boolean matchesAny(char... seq) { if (isEmpty()) return false; bufferUp(); char c = charBuf[bufPos]; for (char seek : seq) { if (seek == c) return true; } return false; } boolean matchesAnySorted(char[] seq) { bufferUp(); return !isEmpty() && Arrays.binarySearch(seq, charBuf[bufPos]) >= 0; } boolean matchesLetter() { if (isEmpty()) return false; char c = charBuf[bufPos]; return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || Character.isLetter(c); } boolean matchesDigit() { if (isEmpty()) return false; char c = charBuf[bufPos]; return (c >= '0' && c <= '9'); } boolean matchConsume(String seq) { bufferUp(); if (matches(seq)) { bufPos += seq.length(); return true; } else { return false; } } boolean matchConsumeIgnoreCase(String seq) { if (matchesIgnoreCase(seq)) { bufPos += seq.length(); return true; } else { return false; } } boolean containsIgnoreCase(String seq) { // used to check presence of </title>, </style>. only finds consistent case. String

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> cached)) { // hit return cached; } else { // hashcode conflict cached = new String(charBuf, start, count); stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again } } return cached; } /** * Check if the value of the provided range equals the string. */ static boolean rangeEquals(final char[] charBuf, final int start, int count, final String cached) { if (count == cached.length()) { int i = start; int j = 0; while (count-- != 0) { if (charBuf[i++] != cached.charAt(j++)) return false; } return true; } return false; } // just used for testing boolean rangeEquals(final int start, final int count, final String cached) { return rangeEquals(charBuf, start, count, cached); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import java.io.IOException; /** * An XML Declaration. */ public class XmlDeclaration extends LeafNode { // todo this impl isn't really right, the data shouldn't be attributes, just a run of text after the name private final boolean isProcessingInstruction; // <! if true, <? if false, declaration (and last data char should be ?) /** * Create a new XML declaration * @param name of declaration * @param isProcessingInstruction is processing instruction */ public XmlDeclaration(String name, boolean isProcessingInstruction) { Validate.notNull(name); value = name; this.isProcessingInstruction = isProcessingInstruction; } /** * Create a new XML declaration * @param name of declaration * @param baseUri Leaf Nodes don't have base URIs; they inherit from their Element * @param isProcessingInstruction is processing instruction * @see XmlDeclaration#XmlDeclaration(String, boolean) * @deprecated */ public XmlDeclaration(String name, String baseUri, boolean isProcessingInstruction) { this(name, isProcessingInstruction); } public String nodeName() { return "#declaration"; } /** * Get the name of this declaration. * @return name of this declaration. */ public String name() { return coreValue(); } /** * Get the unencoded XML declaration. * @return XML declaration */ public String getWholeDeclaration() { StringBuilder sb = StringUtil.borrowBuilder(); try { getWholeDeclaration(sb, new Document.OutputSettings()); } catch (IOException e) { throw new SerializationException(e); } return StringUtil.releaseBuilder(sb).trim(); } private void getWholeDeclaration(Appendable accum, Document.OutputSettings out) throws IOException { for (Attribute attribute : attributes()) { if (!attribute.getKey().equals(nodeName())) { // skips coreValue (name) accum.append(' '); attribute.html(accum, out); } } } void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { accum .

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.parser; import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import java.util.ArrayList; /** * The Tree Builder's current state. Each state embodies the processing for the state, and transitions to other states. */ enum HtmlTreeBuilderState { Initial { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { return true; // ignore whitespace } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { // todo: parse error check on expected doctypes // todo: quirk state check on doctype ids Token.Doctype d = t.asDoctype(); DocumentType doctype = new DocumentType( tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); doctype.setPubSysKey(d.getPubSysKey()); tb.getDocument().appendChild(doctype); if (d.isForceQuirks()) tb.getDocument().quirksMode(Document.QuirksMode.quirks); tb.transition(BeforeHtml); } else { // todo: check not iframe srcdoc tb.transition(BeforeHtml); return tb.process(t); // re-process token } return true; } }, BeforeHtml { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isDoctype()) { tb.error(this); return false; } else if (t.isComment()) { tb.insert(t.asComment()); } else if (isWhitespace(t)) { return true; // ignore whitespace } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { tb.insert(t.asStartTag()); tb.transition(BeforeHead); } else if (t.isEndTag() && (StringUtil.in

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>(t.asEndTag().normalName(), "head", "body", "html", "br"))) { return anythingElse(t, tb); } else if (t.isEndTag()) { tb.error(this); return false; } else { return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.insertStartTag("html"); tb.transition(BeforeHead); return tb.process(t); } }, BeforeHead { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { return true; } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { tb.error(this); return false; } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return InBody.process(t, tb); // does not transition } else if (t.isStartTag() && t.asStartTag().normalName().equals("head")) { Element head = tb.insert(t.asStartTag()); tb.setHeadElement(head); tb.transition(InHead); } else if (t.isEndTag() && (StringUtil.in(t.asEndTag().normalName(), "head", "body", "html", "br"))) { tb.processStartTag("head"); return tb.process(t); } else if (t.isEndTag()) { tb.error(this); return false; } else { tb.processStartTag("head"); return tb.process(t); } return true; } }, InHead { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { tb.insert(t.asCharacter()); return true; } switch (t.type) { case Comment: tb.insert(t.asComment()); break; case Doctype: tb.error(this); return false; case StartTag: Token.StartTag start = t.asStartTag(); String name = start.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>normalName(); if (name.equals("html")) { return InBody.process(t, tb); } else if (StringUtil.in(name, "base", "basefont", "bgsound", "command", "link")) { Element el = tb.insertEmpty(start); // jsoup special: update base the frist time it is seen if (name.equals("base") && el.hasAttr("href")) tb.maybeSetBaseUri(el); } else if (name.equals("meta")) { Element meta = tb.insertEmpty(start); // todo: charset switches } else if (name.equals("title")) { handleRcData(start, tb); } else if (StringUtil.in(name, "noframes", "style")) { handleRawtext(start, tb); } else if (name.equals("noscript")) { // else if noscript && scripting flag = true: rawtext (jsoup doesn't run script, to handle as noscript) tb.insert(start); tb.transition(InHeadNoscript); } else if (name.equals("script")) { // skips some script rules as won't execute them tb.tokeniser.transition(TokeniserState.ScriptData); tb.markInsertionMode(); tb.transition(Text); tb.insert(start); } else if (name.equals("head")) { tb.error(this); return false; } else { return anythingElse(t, tb); } break; case EndTag: Token.EndTag end = t.asEndTag(); name = end.normalName(); if (name.equals("head")) { tb.pop(); tb.transition(AfterHead); } else if (StringUtil.in(name, "body", "html", "br")) { return anythingElse(t, tb); } else { tb.error(this); return false; } break; default: return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, TreeBuilder tb) { tb.processEndTag("head"); return tb.process(t); } }, InHeadNoscript { boolean process(

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Token t, HtmlTreeBuilder tb) { if (t.isDoctype()) { tb.error(this); } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return tb.process(t, InBody); } else if (t.isEndTag() && t.asEndTag().normalName().equals("noscript")) { tb.pop(); tb.transition(InHead); } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "basefont", "bgsound", "link", "meta", "noframes", "style"))) { return tb.process(t, InHead); } else if (t.isEndTag() && t.asEndTag().normalName().equals("br")) { return anythingElse(t, tb); } else if ((t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "head", "noscript")) || t.isEndTag()) { tb.error(this); return false; } else { return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); tb.insert(new Token.Character().data(t.toString())); return true; } }, AfterHead { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { tb.insert(t.asCharacter()); } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { tb.error(this); } else if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); if (name.equals("html")) { return tb.process(t, InBody); } else if (name.equals("body")) { tb.insert(startTag); tb.framesetOk(false); tb.transition(InBody); } else if (name.equals("frame

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>set")) { tb.insert(startTag); tb.transition(InFrameset); } else if (StringUtil.in(name, "base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "title")) { tb.error(this); Element head = tb.getHeadElement(); tb.push(head); tb.process(t, InHead); tb.removeFromStack(head); } else if (name.equals("head")) { tb.error(this); return false; } else { anythingElse(t, tb); } } else if (t.isEndTag()) { if (StringUtil.in(t.asEndTag().normalName(), "body", "html")) { anythingElse(t, tb); } else { tb.error(this); return false; } } else { anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.processStartTag("body"); tb.framesetOk(true); return tb.process(t); } }, InBody { boolean process(Token t, HtmlTreeBuilder tb) { switch (t.type) { case Character: { Token.Character c = t.asCharacter(); if (c.getData().equals(nullString)) { // todo confirm that check tb.error(this); return false; } else if (tb.framesetOk() && isWhitespace(c)) { // don't check if whitespace if frames already closed tb.reconstructFormattingElements(); tb.insert(c); } else { tb.reconstructFormattingElements(); tb.insert(c); tb.framesetOk(false); } break; } case Comment: { tb.insert(t.asComment()); break; } case Doctype: { tb.error(this); return false; } case StartTag: Token.StartTag startTag = t.asStartTag(); // todo - refactor to a switch statement String name = startTag.normalName(); if (name.equals("a")) { if (tb.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>getActiveFormattingElement("a") != null) { tb.error(this); tb.processEndTag("a"); // still on stack? Element remainingA = tb.getFromStack("a"); if (remainingA != null) { tb.removeFromActiveFormattingElements(remainingA); tb.removeFromStack(remainingA); } } tb.reconstructFormattingElements(); Element a = tb.insert(startTag); tb.pushActiveFormattingElements(a); } else if (StringUtil.inSorted(name, Constants.InBodyStartEmptyFormatters)) { tb.reconstructFormattingElements(); tb.insertEmpty(startTag); tb.framesetOk(false); } else if (StringUtil.inSorted(name, Constants.InBodyStartPClosers)) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); } else if (name.equals("span")) { // same as final else, but short circuits lots of checks tb.reconstructFormattingElements(); tb.insert(startTag); } else if (name.equals("li")) { tb.framesetOk(false); ArrayList<Element> stack = tb.getStack(); for (int i = stack.size() - 1; i > 0; i--) { Element el = stack.get(i); if (el.normalName().equals("li")) { tb.processEndTag("li"); break; } if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); } else if (name.equals("html")) { tb.error(this); // merge attributes onto real html Element html = tb.getStack().get(0); for (Attribute attribute : startTag.getAttributes()) { if (!html.hasAttr(attribute.getKey())) html.attributes().put(attribute); } } else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) { return tb

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.process(t, InHead); } else if (name.equals("body")) { tb.error(this); ArrayList<Element> stack = tb.getStack(); if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { // only in fragment case return false; // ignore } else { tb.framesetOk(false); Element body = stack.get(1); for (Attribute attribute : startTag.getAttributes()) { if (!body.hasAttr(attribute.getKey())) body.attributes().put(attribute); } } } else if (name.equals("frameset")) { tb.error(this); ArrayList<Element> stack = tb.getStack(); if (stack.size() == 1 || (stack.size() > 2 && !stack.get(1).normalName().equals("body"))) { // only in fragment case return false; // ignore } else if (!tb.framesetOk()) { return false; // ignore frameset } else { Element second = stack.get(1); if (second.parent() != null) second.remove(); // pop up to html element while (stack.size() > 1) stack.remove(stack.size()-1); tb.insert(startTag); tb.transition(InFrameset); } } else if (StringUtil.inSorted(name, Constants.Headings)) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } if (StringUtil.inSorted(tb.currentElement().normalName(), Constants.Headings)) { tb.error(this); tb.pop(); } tb.insert(startTag); } else if (StringUtil.inSorted(name, Constants.InBodyStartPreListing)) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); tb.reader.matchConsume("\n"); // ignore LF if next token tb.framesetOk(false); } else if (name.equals("form")) { if (tb.getFormElement() != null)

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> { tb.error(this); return false; } if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insertForm(startTag, true); } else if (StringUtil.inSorted(name, Constants.DdDt)) { tb.framesetOk(false); ArrayList<Element> stack = tb.getStack(); for (int i = stack.size() - 1; i > 0; i--) { Element el = stack.get(i); if (StringUtil.inSorted(el.normalName(), Constants.DdDt)) { tb.processEndTag(el.normalName()); break; } if (tb.isSpecial(el) && !StringUtil.inSorted(el.normalName(), Constants.InBodyStartLiBreakers)) break; } if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); } else if (name.equals("plaintext")) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out } else if (name.equals("button")) { if (tb.inButtonScope("button")) { // close and reprocess tb.error(this); tb.processEndTag("button"); tb.process(startTag); } else { tb.reconstructFormattingElements(); tb.insert(startTag); tb.framesetOk(false); } } else if (StringUtil.inSorted(name, Constants.Formatters)) { tb.reconstructFormattingElements(); Element el = tb.insert(startTag); tb.pushActiveFormattingElements(el); } else if (name.equals("nobr")) { tb.reconstructFormattingElements(); if (tb.inScope("nobr")) { tb.error(this); tb.processEndTag("nobr"); tb.reconstructFormattingElements(); } Element el = tb.insert(startTag); tb.pushActiveFormattingElements(el); } else if (StringUtil.in

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Sorted(name, Constants.InBodyStartApplets)) { tb.reconstructFormattingElements(); tb.insert(startTag); tb.insertMarkerToFormattingElements(); tb.framesetOk(false); } else if (name.equals("table")) { if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insert(startTag); tb.framesetOk(false); tb.transition(InTable); } else if (name.equals("input")) { tb.reconstructFormattingElements(); Element el = tb.insertEmpty(startTag); if (!el.attr("type").equalsIgnoreCase("hidden")) tb.framesetOk(false); } else if (StringUtil.inSorted(name, Constants.InBodyStartMedia)) { tb.insertEmpty(startTag); } else if (name.equals("hr")) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.insertEmpty(startTag); tb.framesetOk(false); } else if (name.equals("image")) { if (tb.getFromStack("svg") == null) return tb.process(startTag.name("img")); // change <image> to <img>, unless in svg else tb.insert(startTag); } else if (name.equals("isindex")) { // how much do we care about the early 90s? tb.error(this); if (tb.getFormElement() != null) return false; tb.processStartTag("form"); if (startTag.attributes.hasKey("action")) { Element form = tb.getFormElement(); form.attr("action", startTag.attributes.get("action")); } tb.processStartTag("hr"); tb.processStartTag("label"); // hope you like english. String prompt = startTag.attributes.hasKey("prompt") ? startTag.attributes.get("prompt") : "This is a searchable index. Enter search keywords: "; tb.process(new Token.Character().data(prompt)); // input Attributes

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> inputAttribs = new Attributes(); for (Attribute attr : startTag.attributes) { if (!StringUtil.inSorted(attr.getKey(), Constants.InBodyStartInputAttribs)) inputAttribs.put(attr); } inputAttribs.put("name", "isindex"); tb.processStartTag("input", inputAttribs); tb.processEndTag("label"); tb.processStartTag("hr"); tb.processEndTag("form"); } else if (name.equals("textarea")) { tb.insert(startTag); // todo: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) tb.tokeniser.transition(TokeniserState.Rcdata); tb.markInsertionMode(); tb.framesetOk(false); tb.transition(Text); } else if (name.equals("xmp")) { if (tb.inButtonScope("p")) { tb.processEndTag("p"); } tb.reconstructFormattingElements(); tb.framesetOk(false); handleRawtext(startTag, tb); } else if (name.equals("iframe")) { tb.framesetOk(false); handleRawtext(startTag, tb); } else if (name.equals("noembed")) { // also handle noscript if script enabled handleRawtext(startTag, tb); } else if (name.equals("select")) { tb.reconstructFormattingElements(); tb.insert(startTag); tb.framesetOk(false); HtmlTreeBuilderState state = tb.state(); if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell)) tb.transition(InSelectInTable); else tb.transition(InSelect); } else if (StringUtil.inSorted(name, Constants.InBodyStartOptions)) { if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); tb.reconstructFormattingElements(); tb.insert(startTag);

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> } else if (StringUtil.inSorted(name, Constants.InBodyStartRuby)) { if (tb.inScope("ruby")) { tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals("ruby")) { tb.error(this); tb.popStackToBefore("ruby"); // i.e. close up to but not include name } tb.insert(startTag); } } else if (name.equals("math")) { tb.reconstructFormattingElements(); // todo: handle A start tag whose tag name is "math" (i.e. foreign, mathml) tb.insert(startTag); } else if (name.equals("svg")) { tb.reconstructFormattingElements(); // todo: handle A start tag whose tag name is "svg" (xlink, svg) tb.insert(startTag); } else if (StringUtil.inSorted(name, Constants.InBodyStartDrop)) { tb.error(this); return false; } else { tb.reconstructFormattingElements(); tb.insert(startTag); } break; case EndTag: Token.EndTag endTag = t.asEndTag(); name = endTag.normalName(); if (StringUtil.inSorted(name, Constants.InBodyEndAdoptionFormatters)) { // Adoption Agency Algorithm. for (int i = 0; i < 8; i++) { Element formatEl = tb.getActiveFormattingElement(name); if (formatEl == null) return anyOtherEndTag(t, tb); else if (!tb.onStack(formatEl)) { tb.error(this); tb.removeFromActiveFormattingElements(formatEl); return true; } else if (!tb.inScope(formatEl.normalName())) { tb.error(this); return false; } else if (tb.currentElement() != formatEl) tb.error(this); Element furthestBlock = null; Element commonAncestor = null; boolean seenFormattingElement = false; ArrayList<Element> stack = tb.getStack(); // the spec doesn't limit to < 64, but in degenerate cases (9000+ stack depth

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>) this prevents // run-aways final int stackSize = stack.size(); for (int si = 0; si < stackSize && si < 64; si++) { Element el = stack.get(si); if (el == formatEl) { commonAncestor = stack.get(si - 1); seenFormattingElement = true; } else if (seenFormattingElement && tb.isSpecial(el)) { furthestBlock = el; break; } } if (furthestBlock == null) { tb.popStackToClose(formatEl.normalName()); tb.removeFromActiveFormattingElements(formatEl); return true; } // todo: Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. // does that mean: int pos of format el in list? Element node = furthestBlock; Element lastNode = furthestBlock; for (int j = 0; j < 3; j++) { if (tb.onStack(node)) node = tb.aboveOnStack(node); if (!tb.isInActiveFormattingElements(node)) { // note no bookmark check tb.removeFromStack(node); continue; } else if (node == formatEl) break; Element replacement = new Element(Tag.valueOf(node.nodeName(), ParseSettings.preserveCase), tb.getBaseUri()); // case will follow the original node (so honours ParseSettings) tb.replaceActiveFormattingElement(node, replacement); tb.replaceOnStack(node, replacement); node = replacement; if (lastNode == furthestBlock) { // todo: move the aforementioned bookmark to be immediately after the new node in the list of active formatting elements. // not getting how this bookmark both straddles the element above, but is inbetween here... } if (lastNode.parent() != null) lastNode.remove(); node.appendChild(lastNode); lastNode = node; } if (StringUtil.inSorted(commonAncestor.normalName(), Constants.InBodyEndTableFosters)) { if (lastNode.parent() != null)

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> lastNode.remove(); tb.insertInFosterParent(lastNode); } else { if (lastNode.parent() != null) lastNode.remove(); commonAncestor.appendChild(lastNode); } Element adopter = new Element(formatEl.tag(), tb.getBaseUri()); adopter.attributes().addAll(formatEl.attributes()); Node[] childNodes = furthestBlock.childNodes().toArray(new Node[0]); for (Node childNode : childNodes) { adopter.appendChild(childNode); // append will reparent. thus the clone to avoid concurrent mod. } furthestBlock.appendChild(adopter); tb.removeFromActiveFormattingElements(formatEl); // todo: insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. tb.removeFromStack(formatEl); tb.insertOnStackAfter(furthestBlock, adopter); } } else if (StringUtil.inSorted(name, Constants.InBodyEndClosers)) { if (!tb.inScope(name)) { // nothing to close tb.error(this); return false; } else { tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } } else if (name.equals("span")) { // same as final fall through, but saves short circuit return anyOtherEndTag(t, tb); } else if (name.equals("li")) { if (!tb.inListItemScope(name)) { tb.error(this); return false; } else { tb.generateImpliedEndTags(name); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } } else if (name.equals("body")) { if (!tb.inScope("body")) { tb.error(this); return false; } else { // todo: error if stack contains something not dd, dt, li, optgroup, option, p, rp, rt, tbody, td, tfoot, th, thead, tr

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>, body, html tb.transition(AfterBody); } } else if (name.equals("html")) { boolean notIgnored = tb.processEndTag("body"); if (notIgnored) return tb.process(endTag); } else if (name.equals("form")) { Element currentForm = tb.getFormElement(); tb.setFormElement(null); if (currentForm == null || !tb.inScope(name)) { tb.error(this); return false; } else { tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals(name)) tb.error(this); // remove currentForm from stack. will shift anything under up. tb.removeFromStack(currentForm); } } else if (name.equals("p")) { if (!tb.inButtonScope(name)) { tb.error(this); tb.processStartTag(name); // if no p to close, creates an empty <p></p> return tb.process(endTag); } else { tb.generateImpliedEndTags(name); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } } else if (StringUtil.inSorted(name, Constants.DdDt)) { if (!tb.inScope(name)) { tb.error(this); return false; } else { tb.generateImpliedEndTags(name); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); } } else if (StringUtil.inSorted(name, Constants.Headings)) { if (!tb.inScope(Constants.Headings)) { tb.error(this); return false; } else { tb.generateImpliedEndTags(name); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(Constants.Headings); } } else if (name.equals("sarcasm")) { // *sigh* return anyOtherEndTag(t, tb); } else if (StringUtil

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.inSorted(name, Constants.InBodyStartApplets)) { if (!tb.inScope("name")) { if (!tb.inScope(name)) { tb.error(this); return false; } tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); } } else if (name.equals("br")) { tb.error(this); tb.processStartTag("br"); return false; } else { return anyOtherEndTag(t, tb); } break; case EOF: // todo: error if stack contains something not dd, dt, li, p, tbody, td, tfoot, th, thead, tr, body, html // stop parsing break; } return true; } boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) { String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive ArrayList<Element> stack = tb.getStack(); for (int pos = stack.size() -1; pos >= 0; pos--) { Element node = stack.get(pos); if (node.normalName().equals(name)) { tb.generateImpliedEndTags(name); if (!name.equals(tb.currentElement().normalName())) tb.error(this); tb.popStackToClose(name); break; } else { if (tb.isSpecial(node)) { tb.error(this); return false; } } } return true; } }, Text { // in script, style etc. normally treated as data tags boolean process(Token t, HtmlTreeBuilder tb) { if (t.isCharacter()) { tb.insert(t.asCharacter()); } else if (t.isEOF()) { tb.error(this); // if current node is script: already started tb.pop(); tb.transition(tb.originalState()); return tb.process(t); } else if (t.isEndTag

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>()) { // if: An end tag whose tag name is "script" -- scripting nesting level, if evaluating scripts tb.pop(); tb.transition(tb.originalState()); } return true; } }, InTable { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isCharacter()) { tb.newPendingTableCharacters(); tb.markInsertionMode(); tb.transition(InTableText); return tb.process(t); } else if (t.isComment()) { tb.insert(t.asComment()); return true; } else if (t.isDoctype()) { tb.error(this); return false; } else if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); if (name.equals("caption")) { tb.clearStackToTableContext(); tb.insertMarkerToFormattingElements(); tb.insert(startTag); tb.transition(InCaption); } else if (name.equals("colgroup")) { tb.clearStackToTableContext(); tb.insert(startTag); tb.transition(InColumnGroup); } else if (name.equals("col")) { tb.processStartTag("colgroup"); return tb.process(t); } else if (StringUtil.in(name, "tbody", "tfoot", "thead")) { tb.clearStackToTableContext(); tb.insert(startTag); tb.transition(InTableBody); } else if (StringUtil.in(name, "td", "th", "tr")) { tb.processStartTag("tbody"); return tb.process(t); } else if (name.equals("table")) { tb.error(this); boolean processed = tb.processEndTag("table"); if (processed) // only ignored if in fragment return tb.process(t); } else if (StringUtil.in(name, "style", "script")) { return tb.process(t, InHead); } else if (name.equals("input")) { if (!startTag.attributes.get("type").equalsIgnoreCase("hidden")) { return anythingElse(t, tb); } else { tb.insert

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Empty(startTag); } } else if (name.equals("form")) { tb.error(this); if (tb.getFormElement() != null) return false; else { tb.insertForm(startTag, false); } } else { return anythingElse(t, tb); } return true; // todo: check if should return processed http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intable } else if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); if (name.equals("table")) { if (!tb.inTableScope(name)) { tb.error(this); return false; } else { tb.popStackToClose("table"); } tb.resetInsertionMode(); } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr")) { tb.error(this); return false; } else { return anythingElse(t, tb); } return true; // todo: as above todo } else if (t.isEOF()) { if (tb.currentElement().normalName().equals("html")) tb.error(this); return true; // stops parsing } return anythingElse(t, tb); } boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); boolean processed; if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { tb.setFosterInserts(true); processed = tb.process(t, InBody); tb.setFosterInserts(false); } else { processed = tb.process(t, InBody); } return processed; } }, InTableText { boolean process(Token t, HtmlTreeBuilder tb) { switch (t.type) { case Character: Token.Character c = t.asCharacter(); if (c.getData().

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>equals(nullString)) { tb.error(this); return false; } else { tb.getPendingTableCharacters().add(c.getData()); } break; default: // todo - don't really like the way these table character data lists are built if (tb.getPendingTableCharacters().size() > 0) { for (String character : tb.getPendingTableCharacters()) { if (!isWhitespace(character)) { // InTable anything else section: tb.error(this); if (StringUtil.in(tb.currentElement().normalName(), "table", "tbody", "tfoot", "thead", "tr")) { tb.setFosterInserts(true); tb.process(new Token.Character().data(character), InBody); tb.setFosterInserts(false); } else { tb.process(new Token.Character().data(character), InBody); } } else tb.insert(new Token.Character().data(character)); } tb.newPendingTableCharacters(); } tb.transition(tb.originalState()); return tb.process(t); } return true; } }, InCaption { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isEndTag() && t.asEndTag().normalName().equals("caption")) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); if (!tb.inTableScope(name)) { tb.error(this); return false; } else { tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals("caption")) tb.error(this); tb.popStackToClose("caption"); tb.clearFormattingElementsToLastMarker(); tb.transition(InTable); } } else if (( t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr") || t.isEndTag() && t.asEndTag().normalName().equals("table")) ) { tb.error(this); boolean processed =

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> tb.processEndTag("caption"); if (processed) return tb.process(t); } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr")) { tb.error(this); return false; } else { return tb.process(t, InBody); } return true; } }, InColumnGroup { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { tb.insert(t.asCharacter()); return true; } switch (t.type) { case Comment: tb.insert(t.asComment()); break; case Doctype: tb.error(this); break; case StartTag: Token.StartTag startTag = t.asStartTag(); switch (startTag.normalName()) { case "html": return tb.process(t, InBody); case "col": tb.insertEmpty(startTag); break; default: return anythingElse(t, tb); } break; case EndTag: Token.EndTag endTag = t.asEndTag(); if (endTag.normalName.equals("colgroup")) { if (tb.currentElement().normalName().equals("html")) { // frag case tb.error(this); return false; } else { tb.pop(); tb.transition(InTable); } } else return anythingElse(t, tb); break; case EOF: if (tb.currentElement().normalName().equals("html")) return true; // stop parsing; frag case else return anythingElse(t, tb); default: return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, TreeBuilder tb) { boolean processed = tb.processEndTag("colgroup"); if (processed) // only ignored in frag case return tb.process(t); return true; } }, InTableBody { boolean process(Token t, HtmlTreeBuilder tb) { switch (t

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.type) { case StartTag: Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); if (name.equals("template")) { tb.insert(startTag); } else if (name.equals("tr")) { tb.clearStackToTableBodyContext(); tb.insert(startTag); tb.transition(InRow); } else if (StringUtil.in(name, "th", "td")) { tb.error(this); tb.processStartTag("tr"); return tb.process(startTag); } else if (StringUtil.in(name, "caption", "col", "colgroup", "tbody", "tfoot", "thead")) { return exitTableBody(t, tb); } else return anythingElse(t, tb); break; case EndTag: Token.EndTag endTag = t.asEndTag(); name = endTag.normalName(); if (StringUtil.in(name, "tbody", "tfoot", "thead")) { if (!tb.inTableScope(name)) { tb.error(this); return false; } else { tb.clearStackToTableBodyContext(); tb.pop(); tb.transition(InTable); } } else if (name.equals("table")) { return exitTableBody(t, tb); } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html", "td", "th", "tr")) { tb.error(this); return false; } else return anythingElse(t, tb); break; default: return anythingElse(t, tb); } return true; } private boolean exitTableBody(Token t, HtmlTreeBuilder tb) { if (!(tb.inTableScope("tbody") || tb.inTableScope("thead") || tb.inScope("tfoot"))) { // frag case tb.error(this); return false; } tb.clearStackToTableBodyContext(); tb.processEndTag(tb.currentElement().normalName()); // tbody, tfoot, thead return tb.process(t); } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { return tb

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.process(t, InTable); } }, InRow { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isStartTag()) { Token.StartTag startTag = t.asStartTag(); String name = startTag.normalName(); if (name.equals("template")) { tb.insert(startTag); } else if (StringUtil.in(name, "th", "td")) { tb.clearStackToTableRowContext(); tb.insert(startTag); tb.transition(InCell); tb.insertMarkerToFormattingElements(); } else if (StringUtil.in(name, "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr")) { return handleMissingTr(t, tb); } else { return anythingElse(t, tb); } } else if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); if (name.equals("tr")) { if (!tb.inTableScope(name)) { tb.error(this); // frag return false; } tb.clearStackToTableRowContext(); tb.pop(); // tr tb.transition(InTableBody); } else if (name.equals("table")) { return handleMissingTr(t, tb); } else if (StringUtil.in(name, "tbody", "tfoot", "thead")) { if (!tb.inTableScope(name)) { tb.error(this); return false; } tb.processEndTag("tr"); return tb.process(t); } else if (StringUtil.in(name, "body", "caption", "col", "colgroup", "html", "td", "th")) { tb.error(this); return false; } else { return anythingElse(t, tb); } } else { return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { return tb.process(t, InTable); } private boolean handleMissingTr(Token t, TreeBuilder tb) { boolean processed = tb.processEndTag("tr");

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> if (processed) return tb.process(t); else return false; } }, InCell { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isEndTag()) { Token.EndTag endTag = t.asEndTag(); String name = endTag.normalName(); if (StringUtil.inSorted(name, Constants.InCellNames)) { if (!tb.inTableScope(name)) { tb.error(this); tb.transition(InRow); // might not be in scope if empty: <td /> and processing fake end tag return false; } tb.generateImpliedEndTags(); if (!tb.currentElement().normalName().equals(name)) tb.error(this); tb.popStackToClose(name); tb.clearFormattingElementsToLastMarker(); tb.transition(InRow); } else if (StringUtil.inSorted(name, Constants.InCellBody)) { tb.error(this); return false; } else if (StringUtil.inSorted(name, Constants.InCellTable)) { if (!tb.inTableScope(name)) { tb.error(this); return false; } closeCell(tb); return tb.process(t); } else { return anythingElse(t, tb); } } else if (t.isStartTag() && StringUtil.inSorted(t.asStartTag().normalName(), Constants.InCellCol)) { if (!(tb.inTableScope("td") || tb.inTableScope("th"))) { tb.error(this); return false; } closeCell(tb); return tb.process(t); } else { return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { return tb.process(t, InBody); } private void closeCell(HtmlTreeBuilder tb) { if (tb.inTableScope("td")) tb.processEndTag("td"); else tb.processEndTag("th"); // only here if th or td in scope } }, InSelect { boolean process(Token t, HtmlTreeBuilder tb) {

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> switch (t.type) { case Character: Token.Character c = t.asCharacter(); if (c.getData().equals(nullString)) { tb.error(this); return false; } else { tb.insert(c); } break; case Comment: tb.insert(t.asComment()); break; case Doctype: tb.error(this); return false; case StartTag: Token.StartTag start = t.asStartTag(); String name = start.normalName(); if (name.equals("html")) return tb.process(start, InBody); else if (name.equals("option")) { if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); tb.insert(start); } else if (name.equals("optgroup")) { if (tb.currentElement().normalName().equals("option")) tb.processEndTag("option"); else if (tb.currentElement().normalName().equals("optgroup")) tb.processEndTag("optgroup"); tb.insert(start); } else if (name.equals("select")) { tb.error(this); return tb.processEndTag("select"); } else if (StringUtil.in(name, "input", "keygen", "textarea")) { tb.error(this); if (!tb.inSelectScope("select")) return false; // frag tb.processEndTag("select"); return tb.process(start); } else if (name.equals("script")) { return tb.process(t, InHead); } else { return anythingElse(t, tb); } break; case EndTag: Token.EndTag end = t.asEndTag(); name = end.normalName(); switch (name) { case "optgroup": if (tb.currentElement().normalName().equals("option") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).normalName().equals("optgroup")) tb.processEndTag("option"); if (tb.currentElement().normalName().equals("optgroup")) tb.pop(); else tb

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.error(this); break; case "option": if (tb.currentElement().normalName().equals("option")) tb.pop(); else tb.error(this); break; case "select": if (!tb.inSelectScope(name)) { tb.error(this); return false; } else { tb.popStackToClose(name); tb.resetInsertionMode(); } break; default: return anythingElse(t, tb); } break; case EOF: if (!tb.currentElement().normalName().equals("html")) tb.error(this); break; default: return anythingElse(t, tb); } return true; } private boolean anythingElse(Token t, HtmlTreeBuilder tb) { tb.error(this); return false; } }, InSelectInTable { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isStartTag() && StringUtil.in(t.asStartTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { tb.error(this); tb.processEndTag("select"); return tb.process(t); } else if (t.isEndTag() && StringUtil.in(t.asEndTag().normalName(), "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th")) { tb.error(this); if (tb.inTableScope(t.asEndTag().normalName())) { tb.processEndTag("select"); return (tb.process(t)); } else return false; } else { return tb.process(t, InSelect); } } }, AfterBody { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { return tb.process(t, InBody); } else if (t.isComment()) { tb.insert(t.asComment()); // into html node } else if (t.isDoctype()) { tb.error(this); return false; } else if (t.isStartTag() && t.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>asStartTag().normalName().equals("html")) { return tb.process(t, InBody); } else if (t.isEndTag() && t.asEndTag().normalName().equals("html")) { if (tb.isFragmentParsing()) { tb.error(this); return false; } else { tb.transition(AfterAfterBody); } } else if (t.isEOF()) { // chillax! we're done } else { tb.error(this); tb.transition(InBody); return tb.process(t); } return true; } }, InFrameset { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { tb.insert(t.asCharacter()); } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { tb.error(this); return false; } else if (t.isStartTag()) { Token.StartTag start = t.asStartTag(); switch (start.normalName()) { case "html": return tb.process(start, InBody); case "frameset": tb.insert(start); break; case "frame": tb.insertEmpty(start); break; case "noframes": return tb.process(start, InHead); default: tb.error(this); return false; } } else if (t.isEndTag() && t.asEndTag().normalName().equals("frameset")) { if (tb.currentElement().normalName().equals("html")) { // frag tb.error(this); return false; } else { tb.pop(); if (!tb.isFragmentParsing() && !tb.currentElement().normalName().equals("frameset")) { tb.transition(AfterFrameset); } } } else if (t.isEOF()) { if (!tb.currentElement().normalName().equals("html")) { tb.error(this); return true; } } else { tb.error(this); return false; } return true; } }, After

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Frameset { boolean process(Token t, HtmlTreeBuilder tb) { if (isWhitespace(t)) { tb.insert(t.asCharacter()); } else if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype()) { tb.error(this); return false; } else if (t.isStartTag() && t.asStartTag().normalName().equals("html")) { return tb.process(t, InBody); } else if (t.isEndTag() && t.asEndTag().normalName().equals("html")) { tb.transition(AfterAfterFrameset); } else if (t.isStartTag() && t.asStartTag().normalName().equals("noframes")) { return tb.process(t, InHead); } else if (t.isEOF()) { // cool your heels, we're complete } else { tb.error(this); return false; } return true; } }, AfterAfterBody { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); } else if (t.isEOF()) { // nice work chuck } else { tb.error(this); tb.transition(InBody); return tb.process(t); } return true; } }, AfterAfterFrameset { boolean process(Token t, HtmlTreeBuilder tb) { if (t.isComment()) { tb.insert(t.asComment()); } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals("html"))) { return tb.process(t, InBody); } else if (t.isEOF()) { // nice work chuck } else if (t.isStartTag() && t.asStartTag().normalName().equals("nof

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.html())); } @Test public void testCommentAndDocType() { String xml = "<!DOCTYPE HTML><!-- a comment -->One <qux />Two"; XmlTreeBuilder tb = new XmlTreeBuilder(); Document doc = tb.parse(xml, "http://foo.com/"); assertEquals("<!DOCTYPE HTML><!-- a comment -->One <qux />Two", TextUtil.stripNewlines(doc.html())); } @Test public void testSupplyParserToJsoupClass() { String xml = "<doc><val>One<val>Two</val></bar>Three</doc>"; Document doc = Jsoup.parse(xml, "http://foo.com/", Parser.xmlParser()); assertEquals("<doc><val>One<val>Two</val>Three</val></doc>", TextUtil.stripNewlines(doc.html())); } @Ignore @Test public void testSupplyParserToConnection() throws IOException { String xmlUrl = "http://direct.infohound.net/tools/jsoup-xml-test.xml"; // parse with both xml and html parser, ensure different Document xmlDoc = Jsoup.connect(xmlUrl).parser(Parser.xmlParser()).get(); Document htmlDoc = Jsoup.connect(xmlUrl).parser(Parser.htmlParser()).get(); Document autoXmlDoc = Jsoup.connect(xmlUrl).get(); // check connection auto detects xml, uses xml parser assertEquals("<doc><val>One<val>Two</val>Three</val></doc>", TextUtil.stripNewlines(xmlDoc.html())); assertFalse(htmlDoc.equals(xmlDoc)); assertEquals(xmlDoc, autoXmlDoc); assertEquals(1, htmlDoc.select("head").size()); // html parser normalises assertEquals(0, xmlDoc.select("head").size()); // xml parser does not assertEquals(0, autoXmlDoc.select("head").size()); // xml parser does not } @Test public void testSupplyParserToDataStream() throws IOException, URISyntaxException { File xmlFile = new File(XmlTreeBuilder.class.getResource("/htmltests/xml-test.xml").toURI()); InputStream inStream = new FileInputStream(xmlFile); Document doc = Jsoup.parse(inStream, null, "http://

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> c == '\r' || c == 160; // 160 is &nbsp; (non-breaking space). Not in the spec but expected. } public static boolean isInvisibleChar(int c) { return Character.getType(c) == 16 && (c == 8203 || c == 8204 || c == 8205 || c == 173); // zero width sp, zw non join, zw join, soft hyphen } /** * Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters * (e.g. newline, tab) convert to a simple space * @param string content to normalise * @return normalised string */ public static String normaliseWhitespace(String string) { StringBuilder sb = StringUtil.borrowBuilder(); appendNormalisedWhitespace(sb, string, false); return StringUtil.releaseBuilder(sb); } /** * After normalizing the whitespace within a string, appends it to a string builder. * @param accum builder to append to * @param string string to normalize whitespace within * @param stripLeading set to true if you wish to remove any leading whitespace */ public static void appendNormalisedWhitespace(StringBuilder accum, String string, boolean stripLeading) { boolean lastWasWhite = false; boolean reachedNonWhite = false; int len = string.length(); int c; for (int i = 0; i < len; i+= Character.charCount(c)) { c = string.codePointAt(i); if (isActuallyWhitespace(c)) { if ((stripLeading && !reachedNonWhite) || lastWasWhite) continue; accum.append(' '); lastWasWhite = true; } else if (!isInvisibleChar(c)) { accum.appendCodePoint(c); lastWasWhite = false; reachedNonWhite = true; } } } public static boolean in(final String needle, final String... haystack) { final int len = haystack.length; for (int i = 0; i < len; i++) { if (haystack[i].equals(needle)) return true; } return false

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.parser; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; /** * A character queue with parsing helpers. * * @author Jonathan Hedley */ public class TokenQueue { private String queue; private int pos = 0; private static final char ESC = '\\'; // escape char for chomp balanced. /** Create a new TokenQueue. @param data string of data to back queue. */ public TokenQueue(String data) { Validate.notNull(data); queue = data; } /** * Is the queue empty? * @return true if no data left in queue. */ public boolean isEmpty() { return remainingLength() == 0; } private int remainingLength() { return queue.length() - pos; } /** * Retrieves but does not remove the first character from the queue. * @return First character, or 0 if empty. */ public char peek() { return isEmpty() ? 0 : queue.charAt(pos); } /** Add a character to the start of the queue (will be the next character retrieved). @param c character to add */ public void addFirst(Character c) { addFirst(c.toString()); } /** Add a string to the start of the queue. @param seq string to add. */ public void addFirst(String seq) { // not very performant, but an edge case queue = seq + queue.substring(pos); pos = 0; } /** * Tests if the next characters on the queue match the sequence. Case insensitive. * @param seq String to check queue for. * @return true if the next characters match. */ public boolean matches(String seq) { return queue.regionMatches(true, pos, seq, 0, seq.length()); } /** * Case sensitive match test. * @param seq string to case sensitively check for * @return true if matched, false if not */ public boolean matchesCS(String seq) { return queue.startsWith(seq, pos); } /** Tests if the next characters match any of the sequences. Case insensitive.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> @param seq list of strings to case insensitively check for @return true of any matched, false if none did */ public boolean matchesAny(String... seq) { for (String s : seq) { if (matches(s)) return true; } return false; } public boolean matchesAny(char... seq) { if (isEmpty()) return false; for (char c: seq) { if (queue.charAt(pos) == c) return true; } return false; } public boolean matchesStartTag() { // micro opt for matching "<x" return (remainingLength() >= 2 && queue.charAt(pos) == '<' && Character.isLetter(queue.charAt(pos+1))); } /** * Tests if the queue matches the sequence (as with match), and if they do, removes the matched string from the * queue. * @param seq String to search for, and if found, remove from queue. * @return true if found and removed, false if not found. */ public boolean matchChomp(String seq) { if (matches(seq)) { pos += seq.length(); return true; } else { return false; } } /** Tests if queue starts with a whitespace character. @return if starts with whitespace */ public boolean matchesWhitespace() { return !isEmpty() && StringUtil.isWhitespace(queue.charAt(pos)); } /** Test if the queue matches a word character (letter or digit). @return if matches a word character */ public boolean matchesWord() { return !isEmpty() && Character.isLetterOrDigit(queue.charAt(pos)); } /** * Drops the next character off the queue. */ public void advance() { if (!isEmpty()) pos++; } /** * Consume one character off queue. * @return first character on queue. */ public char consume() { return queue.charAt(pos++); } /** * Consumes the supplied sequence of the queue. If the queue does not start with the supplied sequence, will * throw an illegal state exception -- but you should be running match() against that condition. <p> Case insensitive. * @param seq sequence

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> to remove from head of queue. */ public void consume(String seq) { if (!matches(seq)) throw new IllegalStateException("Queue did not match expected sequence"); int len = seq.length(); if (len > remainingLength()) throw new IllegalStateException("Queue not long enough to consume sequence"); pos += len; } /** * Pulls a string off the queue, up to but exclusive of the match sequence, or to the queue running out. * @param seq String to end on (and not include in return, but leave on queue). <b>Case sensitive.</b> * @return The matched data consumed from queue. */ public String consumeTo(String seq) { int offset = queue.indexOf(seq, pos); if (offset != -1) { String consumed = queue.substring(pos, offset); pos += consumed.length(); return consumed; } else { return remainder(); } } public String consumeToIgnoreCase(String seq) { int start = pos; String first = seq.substring(0, 1); boolean canScan = first.toLowerCase().equals(first.toUpperCase()); // if first is not cased, use index of while (!isEmpty()) { if (matches(seq)) break; if (canScan) { int skip = queue.indexOf(first, pos) - pos; if (skip == 0) // this char is the skip char, but not match, so force advance of pos pos++; else if (skip < 0) // no chance of finding, grab to end pos = queue.length(); else pos += skip; } else pos++; } return queue.substring(start, pos); } /** Consumes to the first sequence provided, or to the end of the queue. Leaves the terminator on the queue. @param seq any number of terminators to consume to. <b>Case insensitive.</b> @return consumed string */ // todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this // is is a case sensitive time... public String consumeToAny(String... seq) { int start = pos; while (!isEmpty() && !matchesAny(

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>seq)) { pos++; } return queue.substring(start, pos); } /** * Pulls a string off the queue (like consumeTo), and then pulls off the matched string (but does not return it). * <p> * If the queue runs out of characters before finding the seq, will return as much as it can (and queue will go * isEmpty() == true). * @param seq String to match up to, and not include in return, and to pull off queue. <b>Case sensitive.</b> * @return Data matched from queue. */ public String chompTo(String seq) { String data = consumeTo(seq); matchChomp(seq); return data; } public String chompToIgnoreCase(String seq) { String data = consumeToIgnoreCase(seq); // case insensitive scan matchChomp(seq); return data; } /** * Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three", * and leave " four" on the queue. Unbalanced openers and closers can be quoted (with ' or ") or escaped (with \). Those escapes will be left * in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for * contains text strings; use unescape for that. * @param open opener * @param close closer * @return data matched from the queue */ public String chompBalanced(char open, char close) { int start = -1; int end = -1; int depth = 0; char last = 0; boolean inSingleQuote = false; boolean inDoubleQuote = false; do { if (isEmpty()) break; Character c = consume(); if (last == 0 || last != ESC) { if (c.equals('\'') && c != open && !inDoubleQuote) inSingleQuote = !inSingleQuote; else if (c.equals('"') && c != open && !inSingleQuote) inDoubleQuote = !inDoubleQuote; if (inSingleQuote || inDoubleQuote) continue;

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> if (c.equals(open)) { depth++; if (start == -1) start = pos; } else if (c.equals(close)) depth--; } if (depth > 0 && last != 0) end = pos; // don't include the outer match pair in the return last = c; } while (depth > 0); final String out = (end >= 0) ? queue.substring(start, end) : ""; if (depth > 0) {// ran out of queue before seeing enough ) Validate.fail("Did not find balanced marker at '" + out + "'"); } return out; } /** * Unescape a \ escaped string. * @param in backslash escaped string * @return unescaped string */ public static String unescape(String in) { StringBuilder out = StringUtil.borrowBuilder(); char last = 0; for (char c : in.toCharArray()) { if (c == ESC) { if (last != 0 && last == ESC) out.append(c); } else out.append(c); last = c; } return StringUtil.releaseBuilder(out); } /** * Pulls the next run of whitespace characters of the queue. * @return Whether consuming whitespace or not */ public boolean consumeWhitespace() { boolean seen = false; while (matchesWhitespace()) { pos++; seen = true; } return seen; } /** * Retrieves the next run of word type (letter or digit) off the queue. * @return String of word characters from queue, or empty string if none. */ public String consumeWord() { int start = pos; while (matchesWord()) pos++; return queue.substring(start, pos); } /** * Consume an tag name off the queue (word or :, _, -) * * @return tag name */ public String consumeTagName() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny(':', '_', '-'))) pos++; return queue.substring(start, pos); } /** * Consume a CSS element selector (tag name, but | instead of : for namespaces (or

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> *| for wildcard namespace), to not conflict with :pseudo selects). * * @return tag name */ public String consumeElementSelector() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny("*|","|", "_", "-"))) pos++; return queue.substring(start, pos); } /** Consume a CSS identifier (ID or class) off the queue (letter, digit, -, _) http://www.w3.org/TR/CSS2/syndata.html#value-def-identifier @return identifier */ public String consumeCssIdentifier() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny('-', '_'))) pos++; return queue.substring(start, pos); } /** Consume an attribute key off the queue (letter, digit, -, _, :") @return attribute key */ public String consumeAttributeKey() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny('-', '_', ':'))) pos++; return queue.substring(start, pos); } /** Consume and return whatever is left on the queue. @return remained of queue. */ public String remainder() { final String remainder = queue.substring(pos, queue.length()); pos = queue.length(); return remainder; } @Override public String toString() { return queue.substring(pos); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>StartTag = startTag.tagName; } else if (token.type == Token.TokenType.EndTag) { Token.EndTag endTag = (Token.EndTag) token; if (endTag.attributes != null) error("Attributes incorrectly present on end tag"); } } void emit(final String str) { // buffer strings up until last string token found, to emit only one token for a run of character refs etc. // does not set isEmitPending; read checks that if (charsString == null) { charsString = str; } else { if (charsBuilder.length() == 0) { // switching to string builder as more than one emit before read charsBuilder.append(charsString); } charsBuilder.append(str); } } void emit(char[] chars) { emit(String.valueOf(chars)); } void emit(int[] codepoints) { emit(new String(codepoints, 0, codepoints.length)); } void emit(char c) { emit(String.valueOf(c)); } TokeniserState getState() { return state; } void transition(TokeniserState state) { this.state = state; } void advanceTransition(TokeniserState state) { reader.advance(); this.state = state; } final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays final private int[] multipointHolder = new int[2]; int[] consumeCharacterReference(Character additionalAllowedCharacter, boolean inAttribute) { if (reader.isEmpty()) return null; if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current()) return null; if (reader.matchesAnySorted(notCharRefCharsSorted)) return null; final int[] codeRef = codepointHolder; reader.mark(); if (reader.matchConsume("#")) { // numbered boolean isHexMode = reader.matchConsumeIgnoreCase("X"); String numRef = isHexMode ? reader.consumeHexSequence() : reader.consumeDigitSequence(); if (numRef.length() == 0) { // didn't match anything characterReferenceError("numeric reference with no numerals"); reader.rewindToMark

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> if (numChars == 1) { codeRef[0] = multipointHolder[0]; return codeRef; } else if (numChars ==2) { return multipointHolder; } else { Validate.fail("Unexpected characters returned for " + nameRef); return multipointHolder; } } } Token.Tag createTagPending(boolean start) { tagPending = start ? startPending.reset() : endPending.reset(); return tagPending; } void emitTagPending() { tagPending.finaliseTag(); emit(tagPending); } void createCommentPending() { commentPending.reset(); } void emitCommentPending() { emit(commentPending); } void createDoctypePending() { doctypePending.reset(); } void emitDoctypePending() { emit(doctypePending); } void createTempBuffer() { Token.reset(dataBuffer); } boolean isAppropriateEndTagToken() { return lastStartTag != null && tagPending.name().equalsIgnoreCase(lastStartTag); } String appropriateEndTagName() { return lastStartTag; // could be null } void error(TokeniserState state) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), "Unexpected character '%s' in input state [%s]", reader.current(), state)); } void eofError(TokeniserState state) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), "Unexpectedly reached end of file (EOF) in input state [%s]", state)); } private void characterReferenceError(String message) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), "Invalid character reference: %s", message)); } void error(String errorMsg) { if (errors.canAddError()) errors.add(new ParseError(reader.pos(), errorMsg)); } boolean currentNodeInHtmlNS() { // todo: implement namespaces correctly return true; // Element currentNode = currentNode(); // return currentNode != null && currentNode.namespace().equals("HTML"); } /** * Utility method to consume reader and unescape entities found within.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> * @param inAttribute if the text to be unescaped is in an attribute * @return unescaped string from reader */ String unescapeEntities(boolean inAttribute) { StringBuilder builder = StringUtil.borrowBuilder(); while (!reader.isEmpty()) { builder.append(reader.consumeTo('&')); if (reader.matches('&')) { reader.consume(); int[] c = consumeCharacterReference(null, inAttribute); if (c == null || c.length==0) builder.append('&'); else { builder.appendCodePoint(c[0]); if (c.length == 2) builder.appendCodePoint(c[1]); } } } return StringUtil.releaseBuilder(builder); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> public static void traverse(NodeVisitor visitor, Elements elements) { Validate.notNull(visitor); Validate.notNull(elements); for (Element el : elements) traverse(visitor, el); } /** * Start a depth-first filtering of the root and all of its descendants. * @param filter Node visitor. * @param root the root node point to traverse. * @return The filter result of the root node, or {@link FilterResult#STOP}. */ public static FilterResult filter(NodeFilter filter, Node root) { Node node = root; int depth = 0; while (node != null) { FilterResult result = filter.head(node, depth); if (result == FilterResult.STOP) return result; // Descend into child nodes: if (result == FilterResult.CONTINUE && node.childNodeSize() > 0) { node = node.childNode(0); ++depth; continue; } // No siblings, move upwards: while (node.nextSibling() == null && depth > 0) { // 'tail' current node: if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { result = filter.tail(node, depth); if (result == FilterResult.STOP) return result; } Node prev = node; // In case we need to remove it below. node = node.parentNode(); depth--; if (result == FilterResult.REMOVE) prev.remove(); // Remove AFTER finding parent. result = FilterResult.CONTINUE; // Parent was not pruned. } // 'tail' current node, then proceed with siblings: if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { result = filter.tail(node, depth); if (result == FilterResult.STOP) return result; } if (node == root) return result; Node prev = node; // In case we need to remove it below. node = node.nextSibling(); if (result == FilterResult.REMOVE) prev.remove(); // Remove AFTER finding sibling. } // root == null? return FilterResult.CONTINUE; } /** * Start a depth-first

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.nodes; import org.jsoup.helper.Validate; import java.util.Collections; import java.util.List; abstract class LeafNode extends Node { private static final List<Node> EmptyNodes = Collections.emptyList(); Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set) protected final boolean hasAttributes() { return value instanceof Attributes; } @Override public final Attributes attributes() { ensureAttributes(); return (Attributes) value; } private void ensureAttributes() { if (!hasAttributes()) { Object coreValue = value; Attributes attributes = new Attributes(); value = attributes; if (coreValue != null) attributes.put(nodeName(), (String) coreValue); } } String coreValue() { return attr(nodeName()); } void coreValue(String value) { attr(nodeName(), value); } @Override public String attr(String key) { Validate.notNull(key); if (!hasAttributes()) { return key.equals(nodeName()) ? (String) value : EmptyString; } return super.attr(key); } @Override public Node attr(String key, String value) { if (!hasAttributes() && key.equals(nodeName())) { this.value = value; } else { ensureAttributes(); super.attr(key, value); } return this; } @Override public boolean hasAttr(String key) { ensureAttributes(); return super.hasAttr(key); } @Override public Node removeAttr(String key) { ensureAttributes(); return super.removeAttr(key); } @Override public String absUrl(String key) { ensureAttributes(); return super.absUrl(key); } @Override public String baseUri() { return hasParent() ? parent().baseUri() : ""; } @Override protected void doSetBaseUri(String baseUri) { // noop } @Override public int childNodeSize() { return 0; } @Override protected List<Node> ensureChildNodes() { return EmptyNodes; } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> r) { switch (r.current()) { case nullChar: t.error(this); r.advance(); t.emit(replacementChar); break; case eof: t.emit(new Token.EOF()); break; default: String data = r.consumeTo(nullChar); t.emit(data); break; } } }, TagOpen { // from < in data void read(Tokeniser t, CharacterReader r) { switch (r.current()) { case '!': t.advanceTransition(MarkupDeclarationOpen); break; case '/': t.advanceTransition(EndTagOpen); break; case '?': t.advanceTransition(BogusComment); break; default: if (r.matchesLetter()) { t.createTagPending(true); t.transition(TagName); } else { t.error(this); t.emit('<'); // char that got us here t.transition(Data); } break; } } }, EndTagOpen { void read(Tokeniser t, CharacterReader r) { if (r.isEmpty()) { t.eofError(this); t.emit("</"); t.transition(Data); } else if (r.matchesLetter()) { t.createTagPending(false); t.transition(TagName); } else if (r.matches('>')) { t.error(this); t.advanceTransition(Data); } else { t.error(this); t.advanceTransition(BogusComment); } } }, TagName { // from < or </ in data, will have start or end tag pending void read(Tokeniser t, CharacterReader r) { // previous TagOpen state did NOT consume, will have a letter char in current //String tagName = r.consumeToAnySorted(tagCharsSorted).toLowerCase(); String tagName = r.consumeTagName(); t.tagPending.appendTagName(tagName); char c = r.consume(); switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': t.transition(BeforeAttributeName); break; case '/':

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> } }, ScriptDataEndTagName { void read(Tokeniser t, CharacterReader r) { handleDataEndTag(t, r, ScriptData); } }, ScriptDataEscapeStart { void read(Tokeniser t, CharacterReader r) { if (r.matches('-')) { t.emit('-'); t.advanceTransition(ScriptDataEscapeStartDash); } else { t.transition(ScriptData); } } }, ScriptDataEscapeStartDash { void read(Tokeniser t, CharacterReader r) { if (r.matches('-')) { t.emit('-'); t.advanceTransition(ScriptDataEscapedDashDash); } else { t.transition(ScriptData); } } }, ScriptDataEscaped { void read(Tokeniser t, CharacterReader r) { if (r.isEmpty()) { t.eofError(this); t.transition(Data); return; } switch (r.current()) { case '-': t.emit('-'); t.advanceTransition(ScriptDataEscapedDash); break; case '<': t.advanceTransition(ScriptDataEscapedLessthanSign); break; case nullChar: t.error(this); r.advance(); t.emit(replacementChar); break; default: String data = r.consumeToAny('-', '<', nullChar); t.emit(data); } } }, ScriptDataEscapedDash { void read(Tokeniser t, CharacterReader r) { if (r.isEmpty()) { t.eofError(this); t.transition(Data); return; } char c = r.consume(); switch (c) { case '-': t.emit(c); t.transition(ScriptDataEscapedDashDash); break; case '<': t.transition(ScriptDataEscapedLessthanSign); break; case nullChar: t.error(this); t.emit(replacementChar); t.transition(ScriptDataEscaped); break; default: t.emit(c); t.transition(ScriptDataEscaped); } } }, ScriptDataEscapedDashDash { void read

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>(Tokeniser t, CharacterReader r) { if (r.isEmpty()) { t.eofError(this); t.transition(Data); return; } char c = r.consume(); switch (c) { case '-': t.emit(c); break; case '<': t.transition(ScriptDataEscapedLessthanSign); break; case '>': t.emit(c); t.transition(ScriptData); break; case nullChar: t.error(this); t.emit(replacementChar); t.transition(ScriptDataEscaped); break; default: t.emit(c); t.transition(ScriptDataEscaped); } } }, ScriptDataEscapedLessthanSign { void read(Tokeniser t, CharacterReader r) { if (r.matchesLetter()) { t.createTempBuffer(); t.dataBuffer.append(r.current()); t.emit("<" + r.current()); t.advanceTransition(ScriptDataDoubleEscapeStart); } else if (r.matches('/')) { t.createTempBuffer(); t.advanceTransition(ScriptDataEscapedEndTagOpen); } else { t.emit('<'); t.transition(ScriptDataEscaped); } } }, ScriptDataEscapedEndTagOpen { void read(Tokeniser t, CharacterReader r) { if (r.matchesLetter()) { t.createTagPending(false); t.tagPending.appendTagName(r.current()); t.dataBuffer.append(r.current()); t.advanceTransition(ScriptDataEscapedEndTagName); } else { t.emit("</"); t.transition(ScriptDataEscaped); } } }, ScriptDataEscapedEndTagName { void read(Tokeniser t, CharacterReader r) { handleDataEndTag(t, r, ScriptDataEscaped); } }, ScriptDataDoubleEscapeStart { void read(Tokeniser t, CharacterReader r) { handleDataDoubleEscapeTag(t, r, ScriptDataDoubleEscaped, ScriptDataEscaped); } }, ScriptDataDoubleEscaped { void read(Tokeniser t, CharacterReader r)

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>; case '\t': case '\n': case '\r': case '\f': case ' ': t.transition(AfterDoctypeName); break; case nullChar: t.error(this); t.doctypePending.name.append(replacementChar); break; case eof: t.eofError(this); t.doctypePending.forceQuirks = true; t.emitDoctypePending(); t.transition(Data); break; default: t.doctypePending.name.append(c); } } }, AfterDoctypeName { void read(Tokeniser t, CharacterReader r) { if (r.isEmpty()) { t.eofError(this); t.doctypePending.forceQuirks = true; t.emitDoctypePending(); t.transition(Data); return; } if (r.matchesAny('\t', '\n', '\r', '\f', ' ')) r.advance(); // ignore whitespace else if (r.matches('>')) { t.emitDoctypePending(); t.advanceTransition(Data); } else if (r.matchConsumeIgnoreCase(DocumentType.PUBLIC_KEY)) { t.doctypePending.pubSysKey = DocumentType.PUBLIC_KEY; t.transition(AfterDoctypePublicKeyword); } else if (r.matchConsumeIgnoreCase(DocumentType.SYSTEM_KEY)) { t.doctypePending.pubSysKey = DocumentType.SYSTEM_KEY; t.transition(AfterDoctypeSystemKeyword); } else { t.error(this); t.doctypePending.forceQuirks = true; t.advanceTransition(BogusDoctype); } } }, AfterDoctypePublicKeyword { void read(Tokeniser t, CharacterReader r) { char c = r.consume(); switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': t.transition(BeforeDoctypePublicIdentifier); break; case '"': t.error(this); // set public id to empty string t.transition(DoctypePublicIdentifier_doubleQuoted); break; case '\'': t.error(this);

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>forceQuirks = true; t.emitDoctypePending(); t.transition(Data); break; case eof: t.eofError(this); t.doctypePending.forceQuirks = true; t.emitDoctypePending(); t.transition(Data); break; default: t.doctypePending.systemIdentifier.append(c); } } }, AfterDoctypeSystemIdentifier { void read(Tokeniser t, CharacterReader r) { char c = r.consume(); switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': break; case '>': t.emitDoctypePending(); t.transition(Data); break; case eof: t.eofError(this); t.doctypePending.forceQuirks = true; t.emitDoctypePending(); t.transition(Data); break; default: t.error(this); t.transition(BogusDoctype); // NOT force quirks } } }, BogusDoctype { void read(Tokeniser t, CharacterReader r) { char c = r.consume(); switch (c) { case '>': t.emitDoctypePending(); t.transition(Data); break; case eof: t.emitDoctypePending(); t.transition(Data); break; default: // ignore char break; } } }, CdataSection { void read(Tokeniser t, CharacterReader r) { String data = r.consumeTo("]]>"); t.dataBuffer.append(data); if (r.matchConsume("]]>") || r.isEmpty()) { t.emit(new Token.CData(t.dataBuffer.toString())); t.transition(Data); }// otherwise, buffer underrun, stay in data section } }; abstract void read(Tokeniser t, CharacterReader r); static final char nullChar = '\u0000'; // char searches. must be sorted, used in inSorted. MUST update TokenisetStateTest if more arrays are added. static final char[] attributeSingleValueCharsSorted = new char[]{nullChar

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>, '&', '\''}; static final char[] attributeDoubleValueCharsSorted = new char[]{nullChar, '"', '&'}; static final char[] attributeNameCharsSorted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '\'', '/', '<', '=', '>'}; static final char[] attributeValueUnquoted = new char[]{nullChar, '\t', '\n', '\f', '\r', ' ', '"', '&', '\'', '<', '=', '>', '`'}; private static final char replacementChar = Tokeniser.replacementChar; private static final String replacementStr = String.valueOf(Tokeniser.replacementChar); private static final char eof = CharacterReader.EOF; /** * Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just * different else exit transitions. */ private static void handleDataEndTag(Tokeniser t, CharacterReader r, TokeniserState elseTransition) { if (r.matchesLetter()) { String name = r.consumeLetterSequence(); t.tagPending.appendTagName(name); t.dataBuffer.append(name); return; } boolean needsExitTransition = false; if (t.isAppropriateEndTagToken() && !r.isEmpty()) { char c = r.consume(); switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': t.transition(BeforeAttributeName); break; case '/': t.transition(SelfClosingStartTag); break; case '>': t.emitTagPending(); t.transition(Data); break; default: t.dataBuffer.append(c); needsExitTransition = true; } } else { needsExitTransition = true; } if (needsExitTransition) { t.emit("</" + t.dataBuffer.toString()); t.transition(elseTransition); } } private static void readData(Tokeniser t, CharacterReader r, TokeniserState current, TokeniserState advance) { switch (r.current()) { case '<': t.advanceTransition(advance); break; case nullChar: t.error(current); r.advance(); t

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.emit(replacementChar); break; case eof: t.emit(new Token.EOF()); break; default: String data = r.consumeToAny('<', nullChar); // todo - why hunt for null here? Just consumeTo'<'? t.emit(data); break; } } private static void readCharRef(Tokeniser t, TokeniserState advance) { int[] c = t.consumeCharacterReference(null, false); if (c == null) t.emit('&'); else t.emit(c); t.transition(advance); } private static void readEndTag(Tokeniser t, CharacterReader r, TokeniserState a, TokeniserState b) { if (r.matchesLetter()) { t.createTagPending(false); t.transition(a); } else { t.emit("</"); t.transition(b); } } private static void handleDataDoubleEscapeTag(Tokeniser t, CharacterReader r, TokeniserState primary, TokeniserState fallback) { if (r.matchesLetter()) { String name = r.consumeLetterSequence(); t.dataBuffer.append(name); t.emit(name); return; } char c = r.consume(); switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': case '/': case '>': if (t.dataBuffer.toString().equals("script")) t.transition(primary); else t.transition(fallback); t.emit(c); break; default: r.unconsume(); t.transition(fallback); } } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.nodes; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Parser; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.ArrayList; import java.util.List; /** A HTML Document. @author Jonathan Hedley, jonathan@hedley.net */ public class Document extends Element { private OutputSettings outputSettings = new OutputSettings(); private Parser parser; // the parser used to parse this document private QuirksMode quirksMode = QuirksMode.noQuirks; private String location; private boolean updateMetaCharset = false; /** Create a new, empty Document. @param baseUri base URI of document @see org.jsoup.Jsoup#parse @see #createShell */ public Document(String baseUri) { super(Tag.valueOf("#root", ParseSettings.htmlDefault), baseUri); this.location = baseUri; } /** Create a valid, empty shell of a document, suitable for adding more elements to. @param baseUri baseUri of document @return document with html, head, and body elements. */ public static Document createShell(String baseUri) { Validate.notNull(baseUri); Document doc = new Document(baseUri); doc.parser = doc.parser(); Element html = doc.appendElement("html"); html.appendElement("head"); html.appendElement("body"); return doc; } /** * Get the URL this Document was parsed from. If the starting URL is a redirect, * this will return the final URL from which the document was served from. * @return location */ public String location() { return location; } /** Accessor to the document's {@code head} element. @return {@code head} */ public Element head() { return findFirstElementByTagName("head", this); } /** Accessor to the document's {@code body} element

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>(this); normaliseStructure("head", htmlEl); normaliseStructure("body", htmlEl); ensureMetaCharsetElement(); return this; } // does not recurse. private void normaliseTextNodes(Element element) { List<Node> toMove = new ArrayList<>(); for (Node node: element.childNodes) { if (node instanceof TextNode) { TextNode tn = (TextNode) node; if (!tn.isBlank()) toMove.add(tn); } } for (int i = toMove.size()-1; i >= 0; i--) { Node node = toMove.get(i); element.removeChild(node); body().prependChild(new TextNode(" ")); body().prependChild(node); } } // merge multiple <head> or <body> contents into one, delete the remainder, and ensure they are owned by <html> private void normaliseStructure(String tag, Element htmlEl) { Elements elements = this.getElementsByTag(tag); Element master = elements.first(); // will always be available as created above if not existent if (elements.size() > 1) { // dupes, move contents to master List<Node> toMove = new ArrayList<>(); for (int i = 1; i < elements.size(); i++) { Node dupe = elements.get(i); toMove.addAll(dupe.ensureChildNodes()); dupe.remove(); } for (Node dupe : toMove) master.appendChild(dupe); } // ensure parented by <html> if (!master.parent().equals(htmlEl)) { htmlEl.appendChild(master); // includes remove() } } // fast method to get first by tag name, used for html, head, body finders private Element findFirstElementByTagName(String tag, Node node) { if (node.nodeName().equals(tag)) return (Element) node; else { int size = node.childNodeSize(); for (int i = 0; i < size; i++) { Element found = findFirstElementByTagName(tag, node.childNode(i)); if (found != null) return found; } } return

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>]").first(); if (metaCharset != null) { metaCharset.attr("charset", charset().displayName()); } else { Element head = head(); if (head != null) { head.appendElement("meta").attr("charset", charset().displayName()); } } // Remove obsolete elements select("meta[name=charset]").remove(); } else if (syntax == OutputSettings.Syntax.xml) { Node node = childNodes().get(0); if (node instanceof XmlDeclaration) { XmlDeclaration decl = (XmlDeclaration) node; if (decl.name().equals("xml")) { decl.attr("encoding", charset().displayName()); final String version = decl.attr("version"); if (version != null) { decl.attr("version", "1.0"); } } else { decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); prependChild(decl); } } else { XmlDeclaration decl = new XmlDeclaration("xml", false); decl.attr("version", "1.0"); decl.attr("encoding", charset().displayName()); prependChild(decl); } } } } /** * A Document's output settings control the form of the text() and html() methods. */ public static class OutputSettings implements Cloneable { /** * The output serialization syntax. */ public enum Syntax {html, xml} private Entities.EscapeMode escapeMode = Entities.EscapeMode.base; private Charset charset; private ThreadLocal<CharsetEncoder> encoderThreadLocal = new ThreadLocal<>(); // initialized by start of OuterHtmlVisitor Entities.CoreCharset coreCharset; // fast encoders for ascii and utf8 private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; private Syntax syntax = Syntax.html; public OutputSettings() { charset(Charset.forName("UTF8")); } /** * Get the document's current HTML escape mode: <code>base</code>, which provides a limited set of named HTML * entities and escapes other characters as numbered entities for maximum compatibility; or <code>extended</code>, * which uses the complete set of HTML named

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>Url(attributeKey.substring("abs:".length())); else return ""; } /** * Get all of the element's attributes. * @return attributes (which implements iterable, in same order as presented in original HTML). */ public abstract Attributes attributes(); /** * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is * <b>case insensitive</b>. The key will be set with case sensitivity as set in the parser settings. * @param attributeKey The attribute key. * @param attributeValue The attribute value. * @return this (for chaining) */ public Node attr(String attributeKey, String attributeValue) { attributeKey = NodeUtils.parser(this).settings().normalizeAttribute(attributeKey); attributes().putIgnoreCase(attributeKey, attributeValue); return this; } /** * Test if this element has an attribute. <b>Case insensitive</b> * @param attributeKey The attribute key to check. * @return true if the attribute exists, false if not. */ public boolean hasAttr(String attributeKey) { Validate.notNull(attributeKey); if (attributeKey.startsWith("abs:")) { String key = attributeKey.substring("abs:".length()); if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).equals("")) return true; } return attributes().hasKeyIgnoreCase(attributeKey); } /** * Remove an attribute from this node. * @param attributeKey The attribute to remove. * @return this (for chaining) */ public Node removeAttr(String attributeKey) { Validate.notNull(attributeKey); attributes().removeIgnoreCase(attributeKey); return this; } /** * Clear (remove) all of the attributes in this node. * @return this, for chaining */ public Node clearAttributes() { Iterator<Attribute> it = attributes().iterator(); while (it.hasNext()) { it.next(); it.remove(); } return this; } /** Get the base URI of this node. @return base URI */ public abstract String baseUri(); /** * Set the baseUri for just this node (not its descendants), if this Node tracks

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> * @return the Document associated with this Node, or null if there is no such Document. */ public Document ownerDocument() { Node root = root(); return (root instanceof Document) ? (Document) root : null; } /** * Remove (delete) this node from the DOM tree. If this node has children, they are also removed. */ public void remove() { Validate.notNull(parentNode); parentNode.removeChild(this); } /** * Insert the specified HTML into the DOM before this node (i.e. as a preceding sibling). * @param html HTML to add before this node * @return this node, for chaining * @see #after(String) */ public Node before(String html) { addSiblingHtml(siblingIndex, html); return this; } /** * Insert the specified node into the DOM before this node (i.e. as a preceding sibling). * @param node to add before this node * @return this node, for chaining * @see #after(Node) */ public Node before(Node node) { Validate.notNull(node); Validate.notNull(parentNode); parentNode.addChildren(siblingIndex, node); return this; } /** * Insert the specified HTML into the DOM after this node (i.e. as a following sibling). * @param html HTML to add after this node * @return this node, for chaining * @see #before(String) */ public Node after(String html) { addSiblingHtml(siblingIndex + 1, html); return this; } /** * Insert the specified node into the DOM after this node (i.e. as a following sibling). * @param node to add after this node * @return this node, for chaining * @see #before(Node) */ public Node after(Node node) { Validate.notNull(node); Validate.notNull(parentNode); parentNode.addChildren(siblingIndex + 1, node); return this; } private void addSiblingHtml(int index, String html) { Validate.notNull(html); Validate.notNull(parentNode); Element context = parent() instanceof Element ? (Element) parent() : null; List<Node>

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> after the node has been unwrapped. Null if the node had no children. * @see #remove() * @see #wrap(String) */ public Node unwrap() { Validate.notNull(parentNode); final List<Node> childNodes = ensureChildNodes(); Node firstChild = childNodes.size() > 0 ? childNodes.get(0) : null; parentNode.addChildren(siblingIndex, this.childNodesAsArray()); this.remove(); return firstChild; } private Element getDeepChild(Element el) { List<Element> children = el.children(); if (children.size() > 0) return getDeepChild(children.get(0)); else return el; } void nodelistChanged() { // Element overrides this to clear its shadow children elements } /** * Replace this node in the DOM with the supplied node. * @param in the node that will will replace the existing node. */ public void replaceWith(Node in) { Validate.notNull(in); Validate.notNull(parentNode); parentNode.replaceChild(this, in); } protected void setParentNode(Node parentNode) { Validate.notNull(parentNode); if (this.parentNode != null) this.parentNode.removeChild(this); this.parentNode = parentNode; } protected void replaceChild(Node out, Node in) { Validate.isTrue(out.parentNode == this); Validate.notNull(in); if (in.parentNode != null) in.parentNode.removeChild(in); final int index = out.siblingIndex; ensureChildNodes().set(index, in); in.parentNode = this; in.setSiblingIndex(index); out.parentNode = null; } protected void removeChild(Node out) { Validate.isTrue(out.parentNode == this); final int index = out.siblingIndex; ensureChildNodes().remove(index); reindexChildren(index); out.parentNode = null; } protected void addChildren(Node... children) { //most used. short circuit addChildren(int), which hits reindex children and array copy final List<Node> nodes = ensureChildNodes(); for (Node child: children) { reparentChild(

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> write to. * @return the supplied {@link Appendable}, for chaining. */ public <T extends Appendable> T html(T appendable) { outerHtml(appendable); return appendable; } /** * Gets this node's outer HTML. * @return outer HTML. * @see #outerHtml() */ public String toString() { return outerHtml(); } protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { accum.append('\n').append(StringUtil.padding(depth * out.indentAmount())); } /** * Check if this node is the same instance of another (object identity test). * @param o other object to compare to * @return true if the content of this node is the same as the other * @see Node#hasSameValue(Object) to compare nodes by their value */ @Override public boolean equals(Object o) { // implemented just so that javadoc is clear this is an identity test return this == o; } /** * Check if this node is has the same content as another node. A node is considered the same if its name, attributes and content match the * other node; particularly its position in the tree does not influence its similarity. * @param o other object to compare to * @return true if the content of this node is the same as the other */ public boolean hasSameValue(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return this.outerHtml().equals(((Node) o).outerHtml()); } /** * Create a stand-alone, deep copy of this node, and all of its children. The cloned node will have no siblings or * parent node. As a stand-alone object, any changes made to the clone or any of its children will not impact the * original node. * <p> * The cloned node may be adopted into another Document or node structure using {@link Element#appendChild(Node)}. * @return a stand-alone cloned node, including clones of any children * @see #shallowClone() */ @Override public Node clone() { Node thisClone = doClone(null); // splits

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> for orphan // Queue up nodes that need their children cloned (BFS). final LinkedList<Node> nodesToProcess = new LinkedList<>(); nodesToProcess.add(thisClone); while (!nodesToProcess.isEmpty()) { Node currParent = nodesToProcess.remove(); final int size = currParent.childNodeSize(); for (int i = 0; i < size; i++) { final List<Node> childNodes = currParent.ensureChildNodes(); Node childClone = childNodes.get(i).doClone(currParent); childNodes.set(i, childClone); nodesToProcess.add(childClone); } } return thisClone; } /** * Create a stand-alone, shallow copy of this node. None of its children (if any) will be cloned, and it will have * no parent or sibling nodes. * @return a single independent copy of this node * @see #clone() */ public Node shallowClone() { return doClone(null); } /* * Return a clone of the node using the given parent (which can be null). * Not a deep copy of children. */ protected Node doClone(Node parent) { Node clone; try { clone = (Node) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } clone.parentNode = parent; // can be null, to create an orphan split clone.siblingIndex = parent == null ? 0 : siblingIndex; return clone; } private static class OuterHtmlVisitor implements NodeVisitor { private Appendable accum; private Document.OutputSettings out; OuterHtmlVisitor(Appendable accum, Document.OutputSettings out) { this.accum = accum; this.out = out; out.prepareEncoder(); } public void head(Node node, int depth) { try { node.outerHtmlHead(accum, depth, out); } catch (IOException exception) { throw new SerializationException(exception); } } public void tail(Node node, int depth) { if (!node.nodeName().equals("#text")) { // saves a void hit. try { node.outerHtmlTail(accum, depth, out

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.select; import org.jsoup.helper.Validate; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.nodes.PseudoTextElement; import org.jsoup.nodes.TextNode; import org.jsoup.nodes.XmlDeclaration; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.jsoup.internal.Normalizer.lowerCase; import static org.jsoup.internal.Normalizer.normalize; /** * Evaluates that an element matches the selector. */ public abstract class Evaluator { protected Evaluator() { } /** * Test if the element meets the evaluator's requirements. * * @param root Root of the matching subtree * @param element tested element * @return Returns <tt>true</tt> if the requirements are met or * <tt>false</tt> otherwise */ public abstract boolean matches(Element root, Element element); /** * Evaluator for tag name */ public static final class Tag extends Evaluator { private String tagName; public Tag(String tagName) { this.tagName = tagName; } @Override public boolean matches(Element root, Element element) { return (element.tagName().equalsIgnoreCase(tagName)); } @Override public String toString() { return String.format("%s", tagName); } } /** * Evaluator for tag name that ends with */ public static final class TagEndsWith extends Evaluator { private String tagName; public TagEndsWith(String tagName) { this.tagName = tagName; } @Override public boolean matches(Element root, Element element) { return (element.tagName().endsWith(tagName)); } @Override public String toString() { return String.format("%s", tagName); } } /** * Evaluator for element id */ public static final class Id extends Evaluator { private String id; public Id(String id) { this.id = id

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>; } @Override public boolean matches(Element root, Element element) { return (id.equals(element.id())); } @Override public String toString() { return String.format("#%s", id); } } /** * Evaluator for element class */ public static final class Class extends Evaluator { private String className; public Class(String className) { this.className = className; } @Override public boolean matches(Element root, Element element) { return (element.hasClass(className)); } @Override public String toString() { return String.format(".%s", className); } } /** * Evaluator for attribute name matching */ public static final class Attribute extends Evaluator { private String key; public Attribute(String key) { this.key = key; } @Override public boolean matches(Element root, Element element) { return element.hasAttr(key); } @Override public String toString() { return String.format("[%s]", key); } } /** * Evaluator for attribute name prefix matching */ public static final class AttributeStarting extends Evaluator { private String keyPrefix; public AttributeStarting(String keyPrefix) { Validate.notEmpty(keyPrefix); this.keyPrefix = lowerCase(keyPrefix); } @Override public boolean matches(Element root, Element element) { List<org.jsoup.nodes.Attribute> values = element.attributes().asList(); for (org.jsoup.nodes.Attribute attribute : values) { if (lowerCase(attribute.getKey()).startsWith(keyPrefix)) return true; } return false; } @Override public String toString() { return String.format("[^%s]", keyPrefix); } } /** * Evaluator for attribute name/value matching */ public static final class AttributeWithValue extends AttributeKeyPair { public AttributeWithValue(String key, String value) { super(key, value); } @Override public boolean matches(Element root, Element element) { return element.hasAttr(key) && value.equalsIgnoreCase(element.attr(key).trim()); } @Override public String toString() { return String.format("[

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>%s=%s]", key, value); } } /** * Evaluator for attribute name != value matching */ public static final class AttributeWithValueNot extends AttributeKeyPair { public AttributeWithValueNot(String key, String value) { super(key, value); } @Override public boolean matches(Element root, Element element) { return !value.equalsIgnoreCase(element.attr(key)); } @Override public String toString() { return String.format("[%s!=%s]", key, value); } } /** * Evaluator for attribute name/value matching (value prefix) */ public static final class AttributeWithValueStarting extends AttributeKeyPair { public AttributeWithValueStarting(String key, String value) { super(key, value); } @Override public boolean matches(Element root, Element element) { return element.hasAttr(key) && lowerCase(element.attr(key)).startsWith(value); // value is lower case already } @Override public String toString() { return String.format("[%s^=%s]", key, value); } } /** * Evaluator for attribute name/value matching (value ending) */ public static final class AttributeWithValueEnding extends AttributeKeyPair { public AttributeWithValueEnding(String key, String value) { super(key, value); } @Override public boolean matches(Element root, Element element) { return element.hasAttr(key) && lowerCase(element.attr(key)).endsWith(value); // value is lower case } @Override public String toString() { return String.format("[%s$=%s]", key, value); } } /** * Evaluator for attribute name/value matching (value containing) */ public static final class AttributeWithValueContaining extends AttributeKeyPair { public AttributeWithValueContaining(String key, String value) { super(key, value); } @Override public boolean matches(Element root, Element element) { return element.hasAttr(key) && lowerCase(element.attr(key)).contains(value); // value is lower case } @Override public String toString() { return String.format("[%s*=%s]", key, value); } } /** * Evaluator for attribute name/

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>a >= 0 && (pos-b)%a==0; } @Override public String toString() { if (a == 0) return String.format(":%s(%d)",getPseudoClass(), b); if (b == 0) return String.format(":%s(%dn)",getPseudoClass(), a); return String.format(":%s(%dn%+d)", getPseudoClass(),a, b); } protected abstract String getPseudoClass(); protected abstract int calculatePosition(Element root, Element element); } /** * css-compatible Evaluator for :eq (css :nth-child) * * @see IndexEquals */ public static final class IsNthChild extends CssNthEvaluator { public IsNthChild(int a, int b) { super(a,b); } protected int calculatePosition(Element root, Element element) { return element.elementSiblingIndex()+1; } protected String getPseudoClass() { return "nth-child"; } } /** * css pseudo class :nth-last-child) * * @see IndexEquals */ public static final class IsNthLastChild extends CssNthEvaluator { public IsNthLastChild(int a, int b) { super(a,b); } @Override protected int calculatePosition(Element root, Element element) { return element.parent().children().size() - element.elementSiblingIndex(); } @Override protected String getPseudoClass() { return "nth-last-child"; } } /** * css pseudo class nth-of-type * */ public static class IsNthOfType extends CssNthEvaluator { public IsNthOfType(int a, int b) { super(a,b); } protected int calculatePosition(Element root, Element element) { int pos = 0; Elements family = element.parent().children(); for (Element el : family) { if (el.tag().equals(element.tag())) pos++; if (el == element) break; }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> return pos; } @Override protected String getPseudoClass() { return "nth-of-type"; } } public static class IsNthLastOfType extends CssNthEvaluator { public IsNthLastOfType(int a, int b) { super(a, b); } @Override protected int calculatePosition(Element root, Element element) { int pos = 0; Elements family = element.parent().children(); for (int i = element.elementSiblingIndex(); i < family.size(); i++) { if (family.get(i).tag().equals(element.tag())) pos++; } return pos; } @Override protected String getPseudoClass() { return "nth-last-of-type"; } } /** * Evaluator for matching the first sibling (css :first-child) */ public static final class IsFirstChild extends Evaluator { @Override public boolean matches(Element root, Element element) { final Element p = element.parent(); return p != null && !(p instanceof Document) && element.elementSiblingIndex() == 0; } @Override public String toString() { return ":first-child"; } } /** * css3 pseudo-class :root * @see <a href="http://www.w3.org/TR/selectors/#root-pseudo">:root selector</a> * */ public static final class IsRoot extends Evaluator { @Override public boolean matches(Element root, Element element) { final Element r = root instanceof Document?root.child(0):root; return element == r; } @Override public String toString() { return ":root"; } } public static final class IsOnlyChild extends Evaluator { @Override public boolean matches(Element root, Element element) { final Element p = element.parent(); return p!=null && !(p instanceof Document) && element.siblingElements().isEmpty(); } @Override public String toString() { return ":only-

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>child"; } } public static final class IsOnlyOfType extends Evaluator { @Override public boolean matches(Element root, Element element) { final Element p = element.parent(); if (p==null || p instanceof Document) return false; int pos = 0; Elements family = p.children(); for (Element el : family) { if (el.tag().equals(element.tag())) pos++; } return pos == 1; } @Override public String toString() { return ":only-of-type"; } } public static final class IsEmpty extends Evaluator { @Override public boolean matches(Element root, Element element) { List<Node> family = element.childNodes(); for (Node n : family) { if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType)) return false; } return true; } @Override public String toString() { return ":empty"; } } /** * Abstract evaluator for sibling index matching * * @author ant */ public abstract static class IndexEvaluator extends Evaluator { int index; public IndexEvaluator(int index) { this.index = index; } } /** * Evaluator for matching Element (and its descendants) text */ public static final class ContainsText extends Evaluator { private String searchText; public ContainsText(String searchText) { this.searchText = lowerCase(searchText); } @Override public boolean matches(Element root, Element element) { return lowerCase(element.text()).contains(searchText); } @Override public String toString() { return String.format(":contains(%s)", searchText); } } /** * Evaluator for matching Element (and its descendants) data */ public static final class ContainsData extends Evaluator { private String searchText; public ContainsData(String searchText) { this.searchText = lowerCase(searchText); } @Override public boolean matches(Element root, Element element) { return lowerCase(element.data()).contains(searchText); } @Override public String toString

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.select; import org.jsoup.internal.StringUtil; import org.jsoup.helper.Validate; import org.jsoup.parser.TokenQueue; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.jsoup.internal.Normalizer.normalize; /** * Parses a CSS selector into an Evaluator tree. */ public class QueryParser { private final static String[] combinators = {",", ">", "+", "~", " "}; private static final String[] AttributeEvals = new String[]{"=", "!=", "^=", "$=", "*=", "~="}; private TokenQueue tq; private String query; private List<Evaluator> evals = new ArrayList<>(); /** * Create a new QueryParser. * @param query CSS query */ private QueryParser(String query) { this.query = query; this.tq = new TokenQueue(query); } /** * Parse a CSS query into an Evaluator. * @param query CSS query * @return Evaluator */ public static Evaluator parse(String query) { try { QueryParser p = new QueryParser(query); return p.parse(); } catch (IllegalArgumentException e) { throw new Selector.SelectorParseException(e.getMessage()); } } /** * Parse the query * @return Evaluator */ Evaluator parse() { tq.consumeWhitespace(); if (tq.matchesAny(combinators)) { // if starts with a combinator, use root as elements evals.add(new StructuralEvaluator.Root()); combinator(tq.consume()); } else { findElements(); } while (!tq.isEmpty()) { // hierarchy and extras boolean seenWhite = tq.consumeWhitespace(); if (tq.matchesAny(combinators)) { combinator(tq.consume()); } else if (seenWhite) { combinator(' '); } else { // E.class, E#id, E[attr] etc. AND findElements(); // take next el, #. etc off queue } } if (evals.size() == 1) return evals.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>replaceRightMostEvaluator(currentEval); else rootEval = currentEval; evals.add(rootEval); } private String consumeSubQuery() { StringBuilder sq = StringUtil.borrowBuilder(); while (!tq.isEmpty()) { if (tq.matches("(")) sq.append("(").append(tq.chompBalanced('(', ')')).append(")"); else if (tq.matches("[")) sq.append("[").append(tq.chompBalanced('[', ']')).append("]"); else if (tq.matchesAny(combinators)) break; else sq.append(tq.consume()); } return StringUtil.releaseBuilder(sq); } private void findElements() { if (tq.matchChomp("#")) byId(); else if (tq.matchChomp(".")) byClass(); else if (tq.matchesWord() || tq.matches("*|")) byTag(); else if (tq.matches("[")) byAttribute(); else if (tq.matchChomp("*")) allElements(); else if (tq.matchChomp(":lt(")) indexLessThan(); else if (tq.matchChomp(":gt(")) indexGreaterThan(); else if (tq.matchChomp(":eq(")) indexEquals(); else if (tq.matches(":has(")) has(); else if (tq.matches(":contains(")) contains(false); else if (tq.matches(":containsOwn(")) contains(true); else if (tq.matches(":containsData(")) containsData(); else if (tq.matches(":matches(")) matches(false); else if (tq.matches(":matchesOwn(")) matches(true); else if (tq.matches(":not(")) not(); else if (tq.matchChomp(":nth-child(")) cssNthChild(false, false); else if (tq.matchChomp(":nth-last-child(")) cssNthChild(true, false); else if (tq.matchChomp(":nth-of-type(")) cssNthChild(false, true); else if (tq.matchChomp(":nth-last-of-type(")) cssN

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.contains("|")) tagName = tagName.replace("|", ":"); evals.add(new Evaluator.Tag(tagName.trim())); } } private void byAttribute() { TokenQueue cq = new TokenQueue(tq.chompBalanced('[', ']')); // content queue String key = cq.consumeToAny(AttributeEvals); // eq, not, start, end, contain, match, (no val) Validate.notEmpty(key); cq.consumeWhitespace(); if (cq.isEmpty()) { if (key.startsWith("^")) evals.add(new Evaluator.AttributeStarting(key.substring(1))); else evals.add(new Evaluator.Attribute(key)); } else { if (cq.matchChomp("=")) evals.add(new Evaluator.AttributeWithValue(key, cq.remainder())); else if (cq.matchChomp("!=")) evals.add(new Evaluator.AttributeWithValueNot(key, cq.remainder())); else if (cq.matchChomp("^=")) evals.add(new Evaluator.AttributeWithValueStarting(key, cq.remainder())); else if (cq.matchChomp("$=")) evals.add(new Evaluator.AttributeWithValueEnding(key, cq.remainder())); else if (cq.matchChomp("*=")) evals.add(new Evaluator.AttributeWithValueContaining(key, cq.remainder())); else if (cq.matchChomp("~=")) evals.add(new Evaluator.AttributeWithValueMatching(key, Pattern.compile(cq.remainder()))); else throw new Selector.SelectorParseException("Could not parse attribute query '%s': unexpected token at '%s'", query, cq.remainder()); } } private void allElements() { evals.add(new Evaluator.AllElements()); } // pseudo selectors :lt, :gt, :eq private void indexLessThan() { evals.add(new Evaluator.IndexLessThan(consumeIndex())); } private void indexGreaterThan() { evals.add(new Evaluator.IndexGreaterThan(consumeIndex())); } private void indexEquals() { evals.add(new Evaluator.IndexEquals(consumeIndex())); } //pseudo selectors :first-

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>child, :last-child, :nth-child, ... private static final Pattern NTH_AB = Pattern.compile("(([+-])?(\\d+)?)n(\\s*([+-])?\\s*\\d+)?", Pattern.CASE_INSENSITIVE); private static final Pattern NTH_B = Pattern.compile("([+-])?(\\d+)"); private void cssNthChild(boolean backwards, boolean ofType) { String argS = normalize(tq.chompTo(")")); Matcher mAB = NTH_AB.matcher(argS); Matcher mB = NTH_B.matcher(argS); final int a, b; if ("odd".equals(argS)) { a = 2; b = 1; } else if ("even".equals(argS)) { a = 2; b = 0; } else if (mAB.matches()) { a = mAB.group(3) != null ? Integer.parseInt(mAB.group(1).replaceFirst("^\\+", "")) : 1; b = mAB.group(4) != null ? Integer.parseInt(mAB.group(4).replaceFirst("^\\+", "")) : 0; } else if (mB.matches()) { a = 0; b = Integer.parseInt(mB.group().replaceFirst("^\\+", "")); } else { throw new Selector.SelectorParseException("Could not parse nth-index '%s': unexpected format", argS); } if (ofType) if (backwards) evals.add(new Evaluator.IsNthLastOfType(a, b)); else evals.add(new Evaluator.IsNthOfType(a, b)); else { if (backwards) evals.add(new Evaluator.IsNthLastChild(a, b)); else evals.add(new Evaluator.IsNthChild(a, b)); } } private int consumeIndex() { String indexS = tq.chompTo(")").trim(); Validate.isTrue(StringUtil.

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>notNull(roots); Evaluator evaluator = QueryParser.parse(query); ArrayList<Element> elements = new ArrayList<>(); IdentityHashMap<Element, Boolean> seenElements = new IdentityHashMap<>(); // dedupe elements by identity, not equality for (Element root : roots) { final Elements found = select(evaluator, root); for (Element el : found) { if (!seenElements.containsKey(el)) { elements.add(el); seenElements.put(el, Boolean.TRUE); } } } return new Elements(elements); } // exclude set. package open so that Elements can implement .not() selector. static Elements filterOut(Collection<Element> elements, Collection<Element> outs) { Elements output = new Elements(); for (Element el : elements) { boolean found = false; for (Element out : outs) { if (el.equals(out)) { found = true; break; } } if (!found) output.add(el); } return output; } /** * Find the first element that matches the query. * @param cssQuery CSS selector * @param root root element to descend into * @return the matching element, or <b>null</b> if none. */ public static Element selectFirst(String cssQuery, Element root) { Validate.notEmpty(cssQuery); return Collector.findFirst(QueryParser.parse(cssQuery), root); } public static class SelectorParseException extends IllegalStateException { public SelectorParseException(String msg, Object... params) { super(String.format(msg, params)); } } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.isHighSurrogate(c)); - but already check above default: return fallback.canEncode(c); } } enum CoreCharset { ascii, utf, fallback; static CoreCharset byName(final String name) { if (name.equals("US-ASCII")) return ascii; if (name.startsWith("UTF-")) // covers UTF-8, UTF-16, et al return utf; return fallback; } } private static void load(EscapeMode e, String pointsData, int size) { e.nameKeys = new String[size]; e.codeVals = new int[size]; e.codeKeys = new int[size]; e.nameVals = new String[size]; int i = 0; CharacterReader reader = new CharacterReader(pointsData); while (!reader.isEmpty()) { // NotNestedLessLess=10913,824;1887& final String name = reader.consumeTo('='); reader.advance(); final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix); final char codeDelim = reader.current(); reader.advance(); final int cp2; if (codeDelim == ',') { cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix); reader.advance(); } else { cp2 = empty; } final String indexS = reader.consumeTo('&'); final int index = Integer.parseInt(indexS, codepointRadix); reader.advance(); e.nameKeys[i] = name; e.codeVals[i] = cp1; e.codeKeys[index] = cp1; e.nameVals[index] = name; if (cp2 != empty) { multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2)); } i++; } Validate.isTrue(i == size, "Unexpected count of entities loaded"); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.nodes; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.helper.HttpConnection; import org.jsoup.helper.Validate; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.List; /** * A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a * form to easily be submitted. */ public class FormElement extends Element { private final Elements elements = new Elements(); /** * Create a new, standalone form element. * * @param tag tag of this element * @param baseUri the base URI * @param attributes initial attributes */ public FormElement(Tag tag, String baseUri, Attributes attributes) { super(tag, baseUri, attributes); } /** * Get the list of form control elements associated with this form. * @return form controls associated with this element. */ public Elements elements() { return elements; } /** * Add a form control element to this form. * @param element form control to add * @return this form element, for chaining */ public FormElement addElement(Element element) { elements.add(element); return this; } @Override protected void removeChild(Node out) { super.removeChild(out); elements.remove(out); } /** * Prepare to submit this form. A Connection object is created with the request set up from the form values. You * can then set up other options (like user-agent, timeout, cookies), then execute it. * @return a connection prepared from the values of this form. * @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the * document's base URI when parsing. */ public Connection submit() { String action = hasAttr("action") ? absUrl("action") : baseUri(); Validate.notEmpty(action, "Could not determine a form action URL for submit. Ensure you set a base URI when parsing."); Connection.Method method = attr("method").toUpperCase().equals("POST") ? Connection.Method

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.POST : Connection.Method.GET; return Jsoup.connect(action) .data(formData()) .method(method); } /** * Get the data that this form submits. The returned list is a copy of the data, and changes to the contents of the * list will not be reflected in the DOM. * @return a list of key vals */ public List<Connection.KeyVal> formData() { ArrayList<Connection.KeyVal> data = new ArrayList<>(); // iterate the form control elements and accumulate their values for (Element el: elements) { if (!el.tag().isFormSubmittable()) continue; // contents are form listable, superset of submitable if (el.hasAttr("disabled")) continue; // skip disabled form inputs String name = el.attr("name"); if (name.length() == 0) continue; String type = el.attr("type"); if ("select".equals(el.tagName())) { Elements options = el.select("option[selected]"); boolean set = false; for (Element option: options) { data.add(HttpConnection.KeyVal.create(name, option.val())); set = true; } if (!set) { Element option = el.select("option").first(); if (option != null) data.add(HttpConnection.KeyVal.create(name, option.val())); } } else if ("checkbox".equalsIgnoreCase(type) || "radio".equalsIgnoreCase(type)) { // only add checkbox or radio if they have the checked attribute if (el.hasAttr("checked")) { final String val = el.val().length() > 0 ? el.val() : "on"; data.add(HttpConnection.KeyVal.create(name, val)); } } else { data.add(HttpConnection.KeyVal.create(name, el.val())); } } return data; } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> { Validate.notNull(tagName); Tag tag = tags.get(tagName); if (tag == null) { tagName = settings.normalizeTag(tagName); Validate.notEmpty(tagName); tag = tags.get(tagName); if (tag == null) { // not defined: create default; go anywhere, do anything! (incl be inside a <p>) tag = new Tag(tagName); tag.isBlock = false; } } return tag; } /** * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything. * <p> * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals(). * </p> * * @param tagName Name of tag, e.g. "p". <b>Case sensitive</b>. * @return The tag, either defined or new generic. */ public static Tag valueOf(String tagName) { return valueOf(tagName, ParseSettings.preserveCase); } /** * Gets if this is a block tag. * * @return if block tag */ public boolean isBlock() { return isBlock; } /** * Gets if this tag should be formatted as a block (or as inline) * * @return if should be formatted as block or inline */ public boolean formatAsBlock() { return formatAsBlock; } /** * Gets if this tag can contain block tags. * * @return if tag can contain block tags * @deprecated No longer used, and no different result than {{@link #isBlock()}} */ public boolean canContainBlock() { return isBlock; } /** * Gets if this tag is an inline tag. * * @return if this tag is an inline tag. */ public boolean isInline() { return !isBlock; } /** * Gets if this tag is a data only tag. * * @return if this tag is a data only tag */ public boolean isData() { return !canContainInline && !isEmpty(); } /** * Get if this is an empty tag * * @return if this is an empty tag

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> */ public boolean isEmpty() { return empty; } /** * Get if this tag is self closing. * * @return if this tag should be output as self closing. */ public boolean isSelfClosing() { return empty || selfClosing; } /** * Get if this is a pre-defined tag, or was auto created on parsing. * * @return if a known tag */ public boolean isKnownTag() { return tags.containsKey(tagName); } /** * Check if this tagname is a known tag. * * @param tagName name of tag * @return if known HTML tag */ public static boolean isKnownTag(String tagName) { return tags.containsKey(tagName); } /** * Get if this tag should preserve whitespace within child text nodes. * * @return if preserve whitespace */ public boolean preserveWhitespace() { return preserveWhitespace; } /** * Get if this tag represents a control associated with a form. E.g. input, textarea, output * @return if associated with a form */ public boolean isFormListed() { return formList; } /** * Get if this tag represents an element that should be submitted with a form. E.g. input, option * @return if submittable with a form */ public boolean isFormSubmittable() { return formSubmit; } Tag setSelfClosing() { selfClosing = true; return this; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Tag)) return false; Tag tag = (Tag) o; if (!tagName.equals(tag.tagName)) return false; if (canContainInline != tag.canContainInline) return false; if (empty != tag.empty) return false; if (formatAsBlock != tag.formatAsBlock) return false; if (isBlock != tag.isBlock) return false; if (preserveWhitespace != tag.preserveWhitespace) return false; if (selfClosing != tag.selfClosing) return false; if (formList != tag.formList) return false; return formSubmit == tag.formSubmit; } @Override public int hashCode() {

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.helper; import java.util.ArrayList; import java.util.Collection; /** * Implementation of ArrayList that watches out for changes to the contents. */ public abstract class ChangeNotifyingArrayList<E> extends ArrayList<E> { public ChangeNotifyingArrayList(int initialCapacity) { super(initialCapacity); } public abstract void onContentsChanged(); @Override public E set(int index, E element) { onContentsChanged(); return super.set(index, element); } @Override public boolean add(E e) { onContentsChanged(); return super.add(e); } @Override public void add(int index, E element) { onContentsChanged(); super.add(index, element); } @Override public E remove(int index) { onContentsChanged(); return super.remove(index); } @Override public boolean remove(Object o) { onContentsChanged(); return super.remove(o); } @Override public void clear() { onContentsChanged(); super.clear(); } @Override public boolean addAll(Collection<? extends E> c) { onContentsChanged(); return super.addAll(c); } @Override public boolean addAll(int index, Collection<? extends E> c) { onContentsChanged(); return super.addAll(index, c); } @Override protected void removeRange(int fromIndex, int toIndex) { onContentsChanged(); super.removeRange(fromIndex, toIndex); } @Override public boolean removeAll(Collection<?> c) { onContentsChanged(); return super.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { onContentsChanged(); return super.retainAll(c); } }

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>package org.jsoup.nodes; import org.jsoup.helper.ChangeNotifyingArrayList; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; import org.jsoup.parser.ParseSettings; import org.jsoup.parser.Tag; import org.jsoup.select.Collector; import org.jsoup.select.Elements; import org.jsoup.select.Evaluator; import org.jsoup.select.NodeFilter; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import org.jsoup.select.QueryParser; import org.jsoup.select.Selector; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import static org.jsoup.internal.Normalizer.normalize; /** * A HTML element consists of a tag name, attributes, and child nodes (including text nodes and * other elements). * * From an Element, you can extract data, traverse the node graph, and manipulate the HTML. * * @author Jonathan Hedley, jonathan@hedley.net */ public class Element extends Node { private static final List<Node> EMPTY_NODES = Collections.emptyList(); private static final Pattern classSplit = Pattern.compile("\\s+"); private Tag tag; private WeakReference<List<Element>> shadowChildrenRef; // points to child elements shadowed from node children List<Node> childNodes; private Attributes attributes; private String baseUri; /** * Create a new, standalone element. * @param tag tag name */ public Element(String tag) { this(Tag.valueOf(tag), "", new Attributes()); } /** * Create a new, standalone Element. (Standalone in that is has no parent.) * * @param tag tag

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> has a key * starting with "data-" is included the dataset. * <p> * E.g., the element {@code <div data-package="jsoup" data-language="Java" class="group">...} has the dataset * {@code package=jsoup, language=java}. * <p> * This map is a filtered view of the element's attribute map. Changes to one map (add, remove, update) are reflected * in the other map. * <p> * You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector. * @return a map of {@code key=value} custom data attributes. */ public Map<String, String> dataset() { return attributes().dataset(); } @Override public final Element parent() { return (Element) parentNode; } /** * Get this element's parent and ancestors, up to the document root. * @return this element's stack of parents, closest first. */ public Elements parents() { Elements parents = new Elements(); accumulateParents(this, parents); return parents; } private static void accumulateParents(Element el, Elements parents) { Element parent = el.parent(); if (parent != null && !parent.tagName().equals("#root")) { parents.add(parent); accumulateParents(parent, parents); } } /** * Get a child element of this element, by its 0-based index number. * <p> * Note that an element can have both mixed Nodes and Elements as children. This method inspects * a filtered list of children that are elements, and the index is based on that filtered list. * </p> * * @param index the index number of the element to retrieve * @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException} * @see #childNode(int) */ public Element child(int index) { return childElementsList().get(index); } /** * Get this element's child elements. * <p> * This is effectively a filter on {@link #childNodes()} to get Element nodes. * </p> * @return child elements. If this element has no children,

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> control regex options. * @return elements matching the supplied regular expression. * @see Element#ownText() */ public Elements getElementsMatchingOwnText(String regex) { Pattern pattern; try { pattern = Pattern.compile(regex); } catch (PatternSyntaxException e) { throw new IllegalArgumentException("Pattern syntax error: " + regex, e); } return getElementsMatchingOwnText(pattern); } /** * Find all elements under this element (including self, and children of children). * * @return all elements */ public Elements getAllElements() { return Collector.collect(new Evaluator.AllElements(), this); } /** * Gets the combined text of this element and all its children. Whitespace is normalized and trimmed. * <p> * For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there now!"} * * @return unencoded, normalized text, or empty string if none. * @see #wholeText() if you don't want the text to be normalized. * @see #ownText() * @see #textNodes() */ public String text() { final StringBuilder accum = StringUtil.borrowBuilder(); NodeTraversor.traverse(new NodeVisitor() { public void head(Node node, int depth) { if (node instanceof TextNode) { TextNode textNode = (TextNode) node; appendNormalisedText(accum, textNode); } else if (node instanceof Element) { Element element = (Element) node; if (accum.length() > 0 && (element.isBlock() || element.tag.getName().equals("br")) && !TextNode.lastCharIsWhitespace(accum)) accum.append(' '); } } public void tail(Node node, int depth) { // make sure there is a space between block tags and immediately following text nodes <div>One</div>Two should be "One Two". if (node instanceof Element) { Element element = (Element) node; if (element.isBlock() && (node.nextSibling() instanceof TextNode) && !TextNode.lastCharIsWhitespace(accum)) accum

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>(text); else StringUtil.appendNormalisedWhitespace(accum, text, TextNode.lastCharIsWhitespace(accum)); } private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { if (element.tag.getName().equals("br") && !TextNode.lastCharIsWhitespace(accum)) accum.append(" "); } static boolean preserveWhitespace(Node node) { // looks only at this element and five levels up, to prevent recursion & needless stack searches if (node instanceof Element) { Element el = (Element) node; int i = 0; do { if (el.tag.preserveWhitespace()) return true; el = el.parent(); i++; } while (i < 6 && el != null); } return false; } /** * Set the text of this element. Any existing contents (text or elements) will be cleared * @param text unencoded text * @return this element */ public Element text(String text) { Validate.notNull(text); empty(); TextNode textNode = new TextNode(text); appendChild(textNode); return this; } /** Test if this element has any text content (that is not just whitespace). @return true if element has non-blank text content. */ public boolean hasText() { for (Node child: childNodes) { if (child instanceof TextNode) { TextNode textNode = (TextNode) child; if (!textNode.isBlank()) return true; } else if (child instanceof Element) { Element el = (Element) child; if (el.hasText()) return true; } } return false; } /** * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. Note that data is NOT the * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@link #data()} * for the contents of scripts, comments, CSS styles, etc. * * @return the data, or empty string if none * * @see #dataNodes() */ public String data() { StringBuilder sb = StringUtil.borrowBuilder();

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> for (Node childNode : childNodes) { if (childNode instanceof DataNode) { DataNode data = (DataNode) childNode; sb.append(data.getWholeData()); } else if (childNode instanceof Comment) { Comment comment = (Comment) childNode; sb.append(comment.getData()); } else if (childNode instanceof Element) { Element element = (Element) childNode; String elementData = element.data(); sb.append(elementData); } else if (childNode instanceof CDataNode) { // this shouldn't really happen because the html parser won't see the cdata as anything special when parsing script. // but incase another type gets through. CDataNode cDataNode = (CDataNode) childNode; sb.append(cDataNode.getWholeText()); } } return StringUtil.releaseBuilder(sb); } /** * Gets the literal value of this element's "class" attribute, which may include multiple class names, space * separated. (E.g. on <code>&lt;div class="header gray"&gt;</code> returns, "<code>header gray</code>") * @return The literal class attribute, or <b>empty string</b> if no class attribute set. */ public String className() { return attr("class").trim(); } /** * Get all of the element's class names. E.g. on element {@code <div class="header gray">}, * returns a set of two elements {@code "header", "gray"}. Note that modifications to this set are not pushed to * the backing {@code class} attribute; use the {@link #classNames(java.util.Set)} method to persist them. * @return set of classnames, empty if no class attribute */ public Set<String> classNames() { String[] names = classSplit.split(className()); Set<String> classNames = new LinkedHashSet<>(Arrays.asList(names)); classNames.remove(""); // if classNames() was empty, would include an empty class return classNames; } /** Set the element's {@code class} attribute to the supplied class names. @param classNames set of classes @return this element, for chaining */ public Element classNames(Set<String

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>> classNames) { Validate.notNull(classNames); if (classNames.isEmpty()) { attributes().remove("class"); } else { attributes().put("class", StringUtil.join(classNames, " ")); } return this; } /** * Tests if this element has a class. Case insensitive. * @param className name of class to check for * @return true if it does, false if not */ // performance sensitive public boolean hasClass(String className) { final String classAttr = attributes().getIgnoreCase("class"); final int len = classAttr.length(); final int wantLen = className.length(); if (len == 0 || len < wantLen) { return false; } // if both lengths are equal, only need compare the className with the attribute if (len == wantLen) { return className.equalsIgnoreCase(classAttr); } // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations) boolean inClass = false; int start = 0; for (int i = 0; i < len; i++) { if (Character.isWhitespace(classAttr.charAt(i))) { if (inClass) { // white space ends a class name, compare it with the requested one, ignore case if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) { return true; } inClass = false; } } else { if (!inClass) { // we're in a class name : keep the start of the substring inClass = true; start = i; } } } // check the last entry if (inClass && len - start == wantLen) { return classAttr.regionMatches(true, start, className, 0, wantLen); } return false; } /** Add a class name to this element's {@code class} attribute. @param className class name to add @return this element */ public Element addClass(String className) { Validate.notNull(className); Set<String> classes = classNames(); classes.add(className); classNames(classes); return this; } /** Remove a class name from

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> this element's {@code class} attribute. @param className class name to remove @return this element */ public Element removeClass(String className) { Validate.notNull(className); Set<String> classes = classNames(); classes.remove(className); classNames(classes); return this; } /** Toggle a class name on this element's {@code class} attribute: if present, remove it; otherwise add it. @param className class name to toggle @return this element */ public Element toggleClass(String className) { Validate.notNull(className); Set<String> classes = classNames(); if (classes.contains(className)) classes.remove(className); else classes.add(className); classNames(classes); return this; } /** * Get the value of a form element (input, textarea, etc). * @return the value of the form element, or empty string if not set. */ public String val() { if (tagName().equals("textarea")) return text(); else return attr("value"); } /** * Set the value of a form element (input, textarea, etc). * @param value value to set * @return this element (for chaining) */ public Element val(String value) { if (tagName().equals("textarea")) text(value); else attr("value", value); return this; } void outerHtmlHead(final Appendable accum, int depth, final Document.OutputSettings out) throws IOException { if (out.prettyPrint() && (tag.formatAsBlock() || (parent() != null && parent().tag().formatAsBlock()) || out.outline())) { if (accum instanceof StringBuilder) { if (((StringBuilder) accum).length() > 0) indent(accum, depth, out); } else { indent(accum, depth, out); } } accum.append('<').append(tagName()); if (attributes != null) attributes.html(accum, out); // selfclosing includes unknown tags, isEmpty defines tags that are always empty if (childNodes.isEmpty() && tag.isSelfClosing()) { if (out.syntax() == Document.OutputSettings.Syntax.html && tag.isEmpty()) accum.append('>'); else accum

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>.append(" />"); // <img> in html, <img /> in xml } else accum.append('>'); } void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException { if (!(childNodes.isEmpty() && tag.isSelfClosing())) { if (out.prettyPrint() && (!childNodes.isEmpty() && ( tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && !(childNodes.get(0) instanceof TextNode)))) ))) indent(accum, depth, out); accum.append("</").append(tagName()).append('>'); } } /** * Retrieves the element's inner HTML. E.g. on a {@code <div>} with one empty {@code <p>}, would return * {@code <p></p>}. (Whereas {@link #outerHtml()} would return {@code <div><p></p></div>}.) * * @return String of HTML. * @see #outerHtml() */ public String html() { StringBuilder accum = StringUtil.borrowBuilder(); html(accum); String html = StringUtil.releaseBuilder(accum); return NodeUtils.outputSettings(this).prettyPrint() ? html.trim() : html; } @Override public <T extends Appendable> T html(T appendable) { final int size = childNodes.size(); for (int i = 0; i < size; i++) childNodes.get(i).outerHtml(appendable); return appendable; } /** * Set this element's inner HTML. Clears the existing HTML first. * @param html HTML to parse and set into this element * @return this element * @see #append(String) */ public Element html(String html) { empty(); append(html); return this; } @Override public Element clone() { return (Element) super.clone(); } @Override public Element shallowClone() { // simpler than implementing a clone version with no child copy return new Element(tag, baseUri, attributes == null ? null : attributes.clone()); } @Override protected Element doClone(Node parent) { Element clone

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS>). This * provides stack context (for implicit element creation). * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs. * @param errorList list to add errors to * * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified. */ public static List<Node> parseFragment(String fragmentHtml, Element context, String baseUri, ParseErrorList errorList) { HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder(); Parser parser = new Parser(treeBuilder); parser.errors = errorList; return treeBuilder.parseFragment(fragmentHtml, context, baseUri, parser); } /** * Parse a fragment of XML into a list of nodes. * * @param fragmentXml the fragment of XML to parse * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs. * @return list of nodes parsed from the input XML. */ public static List<Node> parseXmlFragment(String fragmentXml, String baseUri) { XmlTreeBuilder treeBuilder = new XmlTreeBuilder(); return treeBuilder.parseFragment(fragmentXml, baseUri, new Parser(treeBuilder)); } /** * Parse a fragment of HTML into the {@code body} of a Document. * * @param bodyHtml fragment of HTML * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs. * * @return Document, with empty head, and HTML parsed into body */ public static Document parseBodyFragment(String bodyHtml, String baseUri) { Document doc = Document.createShell(baseUri); Element body = doc.body(); List<Node> nodeList = parseFragment(bodyHtml, body, baseUri); Node[] nodes = nodeList.toArray(new Node[0]); // the node list gets modified when re-parented for (int i = nodes.length - 1; i > 0; i--) { nodes[i].remove(); } for (Node node : nodes) { body.appendChild(node); } return doc; } /** * Utility method to unescape HTML entities from a string * @param string HTML escaped string

Jsoup, 92

<FILEB>
<CHANGES>
import org.jsoup.parser.ParseSettings;
<CHANGEE>
<CHANGES>
public Attributes add(String key, String value) {
<CHANGEE>
<CHANGES>
return this;
<CHANGEE>
<CHANGES>
public boolean isEmpty() {
return size == 0;
}
<CHANGEE>
<CHANGES>
public int deduplicate(ParseSettings settings) {
if (isEmpty())
return 0;
boolean preserve = settings.preserveAttributeCase();
int dupes = 0;
OUTER: for (int i = 0; i < keys.length; i++) {
for (int j = i + 1; j < keys.length; j++) {
if (keys[j] == null)
continue OUTER; // keys.length doesn't shrink when removing, so re-test
if ((preserve && keys[i].equals(keys[j])) || (!preserve && keys[i].equalsIgnoreCase(keys[j]))) {
dupes++;
remove(j);
j--;
}
}
}
return dupes;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (!startTag.attributes.isEmpty()) {
int dupes = startTag.attributes.deduplicate(settings);
if (dupes > 0) {
error("Duplicate attribute");
}
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
public boolean preserveAttributeCase() {
return preserveAttributeCase;
}
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
attributes.add(pendingAttributeName, value);
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
startTag.attributes.deduplicate(settings);
<CHANGEE>
<FILEE>
<FILEB> package org.jsoup.nodes; import org.jsoup.SerializationException; import org.jsoup.helper.Validate; import org.jsoup.internal.StringUtil; <CHANGES> <CHANGEE> import java.io.IOException; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.jsoup.internal.Normalizer.lowerCase; /** */ public String get(String key) { int i = indexOfKey(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Get an attribute's value by case-insensitive key * @param key the attribute name * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute). */ public String getIgnoreCase(String key) { int i = indexOfKeyIgnoreCase(key); return i == NotFound ? EmptyString : checkNotNull(vals[i]); } /** * Adds a new attribute. Will produce duplicates if the key already exists. * @see Attributes#put(String, String) */ <CHANGES> private void add(String key, String value) { <CHANGEE> checkCapacity(size + 1); keys[size] = key; vals[size] = value; size++; <CHANGES> <CHANGEE> } /** * Set a new attribute, or replace an existing one by key. * @param key case sensitive attribute key * @param value attribute value * @return these attributes, for chaining */ public Attributes put(String key, String value) { int i = indexOfKey(key); if (i != NotFound) vals[i] = value; else /** Tests if these attributes contain an attribute with this key. @param key key to check for @return true if key exists, false otherwise */ public boolean hasKeyIgnoreCase(String key) { return indexOfKeyIgnoreCase(key) != NotFound; } /** Get the number of attributes in this set. @return size */ public int size()<SCANS> key and a HTML attribute encoded value. * @param unencodedKey assumes the key is not encoded, as can be only run of simple \w chars. * @param encodedValue HTML attribute encoded value * @return attribute */ public static Attribute createFromEncoded(String unencodedKey, String encodedValue) { String value = Entities.unescape(encodedValue, true); return new Attribute(unencodedKey, value, null); // parent will get set when Put } protected boolean isDataAttribute() { return isDataAttribute(key); } protected static boolean isDataAttribute(String key) { return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length(); } /** * Collapsible if it's a boolean attribute and value is empty or same as name * * @param out output settings * @return Returns whether collapsible or not */ protected final boolean shouldCollapseAttribute(Document.OutputSettings out) { return shouldCollapseAttribute(key, val, out); } protected static boolean shouldCollapseAttribute(final String key, final String val, final Document.OutputSettings out) { return ( out.syntax() == Document.OutputSettings.Syntax.html && (val == null || ("".equals(val) || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key))); } /** * @deprecated */ protected boolean isBooleanAttribute() { return Arrays.binarySearch(booleanAttributes, key) >= 0 || val == null; } /** * Checks if this attribute name is defined as a boolean attribute in HTML5 */ protected static boolean isBooleanAttribute(final String key) { return Arrays.binarySearch(booleanAttributes, key) >= 0; } @Override public boolean equals(Object o) { // note parent not considered if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Attribute attribute = (Attribute) o; if (key != null ? !key.equals(attribute.key) : attribute.key != null) return false; return val != null ? val.equals(attribute.val) : attribute.val == null; } @Override public int hashCode() { // note parent not considered